<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>R-bloggers</title>
	<atom:link href="https://www.r-bloggers.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.r-bloggers.com</link>
	<description>R news and tutorials contributed by hundreds of R bloggers</description>
	<lastBuildDate>Mon, 26 Oct 2026 00:00:00 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.23</generator>

<image>
	<url>https://i0.wp.com/www.r-bloggers.com/wp-content/uploads/2016/08/cropped-R_single_01-200.png?fit=32%2C32&#038;ssl=1</url>
	<title>R-bloggers</title>
	<link>https://www.r-bloggers.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">11524731</site>	<item>
		<title>Battles and space-filling curves</title>
		<link>https://www.r-bloggers.com/2026/10/battles-and-space-filling-curves/</link>
		
		<dc:creator><![CDATA[Data Science Confidential - r]]></dc:creator>
		<pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://datascienceconfidential.github.io/r/graphics/2026/10/26/space-filling-curves.html</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> I recently learned about an interesting way of reducing the dimension of a data set. I’d like to illustrate it with a data set of about 550 historical battles in the UK, extracted from Wikidata using a SPARQL query. No doubt some readers will have seen...</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/battles-and-space-filling-curves/">Battles and space-filling curves</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://datascienceconfidential.github.io/r/graphics/2026/10/26/space-filling-curves.html"> Data Science Confidential - r</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
<p>I recently learned about an interesting way of reducing the dimension of a data set. I’d like to illustrate it with a data set of about 550 historical battles in the UK, extracted from <a href="https://www.wikidata.org/wiki/Wikidata:Main_Page" rel="nofollow" target="_blank">Wikidata</a> using a SPARQL query. No doubt some readers will have seen this idea before, but it’s new to me and I found it pretty.</p>

<p>The following plot shows the raw data, with a crossed swords symbol for the location of each battle. It’s possible to see some patterns on the plot (for example, it probably wasn’t much fun to live on the borders of mediaeval Wales) but it could be clearer.</p>

<div style="width:90%; margin:0 auto;">
 <img src="https://i2.wp.com/datascienceconfidential.github.io/blog/images/2026/battle_map.png?w=578&#038;ssl=1" data-recalc-dims="1" />
</div>

<p>One way to visualise battles over time is to make a 2D graph with time on one axis and location on another axis. But how do you plot a 2D location on a 1D line? One answer is to use a space-filling curve, which in this case will be a curve which snakes all over the map, visiting every location once. This won’t necessarily preserve the relationship between points (points which are close together in 2D could be far apart in 1D) but at least points which are close together in 1D are guaranteed to be close together in 2D.</p>

<p>One example of a space-filling curve is the <a href="https://en.wikipedia.org/wiki/Hilbert_curve" rel="nofollow" target="_blank">Hilbert curve</a>. A typical Hilbert curve can be created by writing a function which takes an integer <code>n</code> as input and produces a $2^n \times 2^n$ matrix like this:</p>

\[\left[
\begin{matrix}
   0  &#038;  3  &#038;    4  &#038;    5  &#038;   58  &#038;   59  &#038;   60  &#038;   63\\
   1  &#038;  2  &#038;    7   &#038;   6  &#038;   57  &#038;   56  &#038;   61  &#038;   62\\
  14  &#038; 13  &#038;    8  &#038;   9   &#038;  54  &#038;   55   &#038;  50  &#038;   49\\
  15  &#038; 12  &#038;   11   &#038;  10   &#038;  53  &#038;   52   &#038;  51  &#038;   48\\
   16 &#038;  17  &#038;   30  &#038;   31  &#038;   32  &#038;   33  &#038;   46  &#038;   47\\
  19  &#038; 18  &#038;   29  &#038;   28   &#038;  35  &#038;   34   &#038;  45  &#038;   44\\
  20  &#038; 23  &#038;   24  &#038;   27  &#038;   36   &#038;  39  &#038;   40  &#038;   43\\
  21 &#038;  22  &#038;   25   &#038;  26  &#038;   37   &#038;  38   &#038;  41   &#038;  42\\
\end{matrix}
\right]\]

<p>This matrix should be interpreted as a curve which starts at zero and follows the numbers in order, like this:</p>

<div style="width:80%; margin:0 auto;">
 <img src="https://i2.wp.com/datascienceconfidential.github.io/blog/images/2026/hilbert_curve_example.png?w=578&#038;ssl=1" data-recalc-dims="1" />
</div>

<p>As <code>n</code> increases, the curve becomes more and more complicated. The visualisation below uses a $64 \times 64$ grid and a curve like this:</p>

<div style="width:80%; margin:0 auto;">
 <img src="https://i1.wp.com/datascienceconfidential.github.io/blog/images/2026/hilbert_curve_example_2.png?w=578&#038;ssl=1" data-recalc-dims="1" />
</div>

<p>For the final visualisation, I used a rectangle between $-6$ and $1.8$ degrees longitude and $49.8$ and $58$ latitude. I added the locations of some cities to the plot and plotted squares which were mostly sea as blue lines.</p>

<div style="width:97%; margin:0 auto;">
 <img src="https://i0.wp.com/datascienceconfidential.github.io/blog/images/2026/battle_plot.png?w=578&#038;ssl=1" data-recalc-dims="1" />
</div>

<p>I quite like the visualisation. Firstly, you can see some of the geography. There are three clusters of points which, going from left to right, seem to correspond roughly to Wales, Scotland, and England (the original data contained part of Ireland, but I cut out most of it using the bounding rectangle). Periods of extended conflict over a particular place appear as vertical columns of points. When there was a general conflict over a wide area, you see a horizontal line of points. Some features of the data are much more obvious on this visualisation than on the original map.</p>

<div style="width:97%; margin:0 auto;">
 <img src="https://i1.wp.com/datascienceconfidential.github.io/blog/images/2026/battle_plot_with_markup.png?w=578&#038;ssl=1" data-recalc-dims="1" />
</div>

<p>I’ve highlighted a few interesting features. The most obvious is the line of points highlighted in yellow, which correspond to the <a href="https://en.wikipedia.org/wiki/English_Civil_War" rel="nofollow" target="_blank">English Civil War</a>. The points highlighted in purple are the <a href="https://www.visitscotland.com/things-to-do/culture-history/jacobites" rel="nofollow" target="_blank">Jacobite Rebellion</a>, which up to now were the last major land battles in the UK. The long <a href="https://www.historic-uk.com/HistoryUK/HistoryofWales/The-English-conquest-of-Wales/" rel="nofollow" target="_blank">Conquest of Wales</a> which was also visible on the original map is highlighted in turquoise.</p>

<p>Various other patterns can be detected by staring at the map. The weird-looking row of points near London highlighted in green are various battles from the twentieth century, mostly associated with the two world wars, including the Battle of Britain and various naval engagements.</p>

<p>Overall, I thought this was a very interesting technique, and I wonder where it comes from. Who came up with the idea of using a space-filling curve for dimension reduction? Is there an optimal way to do it (i.e. given some data, is there a “best” way of choosing a space-filling curve to minimise distortion in your plot?) I don’t know!</p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://datascienceconfidential.github.io/r/graphics/2026/10/26/space-filling-curves.html"> Data Science Confidential - r</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/battles-and-space-filling-curves/">Battles and space-filling curves</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404470</post-id>	</item>
		<item>
		<title>Dogs of New York, Shapes and Sizes Edition</title>
		<link>https://www.r-bloggers.com/2026/10/dogs-of-new-york-shapes-and-sizes-edition/</link>
		
		<dc:creator><![CDATA[R on kieranhealy.org]]></dc:creator>
		<pubDate>Sun, 25 Oct 2026 17:57:06 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/</guid>

					<description><![CDATA[<p>My recent Dogs of New<br />
York, post<br />
got circulated a little. On Friday I got a very nice note from William<br />
Kornblum, an urban and<br />
community studies sociologist who these days is emeritus at CUNY. In the 1980s,<br />
Bill studied the people who use Central Park ...</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/dogs-of-new-york-shapes-and-sizes-edition/">Dogs of New York, Shapes and Sizes Edition</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/"> R on kieranhealy.org</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
<p>My recent <a href="https://kieranhealy.org/blog/archives/2026/10/14/dogs-of-new-york/" rel="nofollow" target="_blank">Dogs of New
York</a>, post
got circulated a little. On Friday I got a very nice note from <a href="https://www.gc.cuny.edu/people/william-kornblum" rel="nofollow" target="_blank">William
Kornblum</a>, an urban and
community studies sociologist who these days is emeritus at CUNY. In the 1980s,
Bill studied the people who use Central Park and wrote a report for the Central
Park Conservancy. Some of that work counted dog-walkers.</p>
<p>He reminded me that parks in New York City see a lot of dogs. <a href="https://www.centralparknyc.org/articles/dogscentralpark" rel="nofollow" target="_blank">NYC Parks
estimates</a> about 1.5
million canine visits to Central Park each year. That is about 4,100 visits a
day, to one park. He also asked:</p>



<blockquote>
    <p>Is there a size gradient perhaps? Larger dogs out on the periphery where there
is more room? Smaller dogs in the dense apartment neighborhoods?</p>

</blockquote>

<p>This is a good question; one the data I have can <em>almost</em> answer. The main
problem is that the license data contained in
<a href="https://kjhealy.github.io/nycdogs/" rel="nofollow" target="_blank"><code>nycdogs</code></a> do not record the size or weight
of any particular dog. Licenses do record the breed that the owner reports
(though, <a href="https://kieranhealy.org/blog/archives/2026/10/14/dogs-of-new-york/" rel="nofollow" target="_blank">as we saw
before</a> the
modal breed reported is “Unknown”). But let’s see how far we can get by
leveraging our knowledge of breeds.</p>
<p>To begin we need to give each breed a size. For this I used the American Kennel
Club’s breed weights. The AKC lists a weight range for each breed it recognizes,
from its breed standards. Tiago Filho <a href="https://github.com/tmfilho/akcdata" rel="nofollow" target="_blank">collected these into one
table</a> for 277 breeds in 2020. I had to make
a few adjustments to reconcile it with the breeds recorded in <code>nycdogs</code>, most
notably for two popular city breeds. First, the AKC lists one Dachshund range
that covers both sizes within this breed. Owners often record “Miniature” in the
breed name, so I gave those dogs the AKC miniature limit of 11 pounds. Second,
Pit Bulls are among the most common dogs in the city, but to my knowledge the
AKC does not register them. I used the United Kennel Club’s <a href="https://www.ukcdogs.com/american-pit-bull-terrier" rel="nofollow" target="_blank">American Pit Bull
Terrier standard</a> instead.</p>
<p>I assigned each breed a “typical weight”, which is the middle of its range. Some
standards give only one figure, such as “not more than 6 pounds” for a
Chihuahua. In those cases I used that number. This is not ideal. A breed
standard describes a show dog, not the dog you see in the park. (Though, of
course, in some NYC parks those might be the same. But not in general.) Real
dogs will be heavier or lighter than their standard. Still, a Great Dane is
reliably bigger than a Yorkie, and it’s the relative differences we’re
interested in.</p>
<p>The second problem is that many dogs have no breed we can use. I work with an
approximation of one record per dog, as in the first post, which gives about
358,000 dogs. I can only assign a breed size to about 60 percent of them:</p>
<ul>
<li>About 22 percent are crossbreeds, such as “Labrador Retriever Crossbreed”. A
mix could be any size. I chose not to guess.</li>
<li>About 10 percent have no breed recorded, mostly “Unknown”.</li>
<li>About 6 percent are “designer” breeds, such as the up-and-coming
Goldendoodles, Maltipoos, and Morkies. No kennel club sets a standard for
these.</li>
<li>About 1.5 percent are Poodles with no size given. A Toy Poodle and a Standard
Poodle differ by about 50 pounds, so again, I chose to drop these.</li>
</ul>
<p>Now, if the missing dogs were spread evenly across the city, the missingness
would not matter all that much. We can try to check that. Here is a map showing
the share of dogs in each ZIP code that have a breed size.</p>
<figure class="full-width"><a href="https://i1.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-coverage-zip.png?ssl=1" rel="nofollow" target="_blank">
    <img src="https://i1.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-coverage-zip.png?w=578&#038;ssl=1"
         alt="Map of the share of licensed dogs in each ZIP code that have a breed size. Most ZIP codes are between about half and two-thirds." data-recalc-dims="1"/></a>
</figure>
<p>Coverage runs from about half to about two-thirds of dogs in each ZIP code. It
is highest on the West Side of Manhattan and in parts of eastern Queens. It is
lowest in central Brooklyn. So, dogs are not
<a href="https://stefvanbuuren.name/fimd/sec-MCAR.html" rel="nofollow" target="_blank">MCAR</a> in the city. The maps and
analysis below is restricted to describing dogs with a known breed, and that
group is not quite the same in every part of the city.</p>
<h2 id="where-the-big-dogs-are">Where the big dogs are</h2>
<p>For each ZIP code with at least 50 dogs of known size, I calculated the median
typical weight. (As before, I use the city’s MODZCTAs, which are ZIP code areas
with some small ones merged.)</p>
<figure class="full-width"><a href="https://i0.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-median-zip.png?ssl=1" rel="nofollow" target="_blank">
    <img src="https://i0.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-median-zip.png?w=578&#038;ssl=1"
         alt="Median typical breed weight of licensed dogs by ZIP code. The largest medians are in lower Manhattan, brownstone Brooklyn, Woodlawn and City Island in the Bronx, and the Rockaways. The smallest are in most of the Bronx, southeast Queens" data-recalc-dims="1"/></a>
</figure>
<p>I didn’t try to make the weight categories too fine-grained. Again, the weights
are derived from the breed standard, not individual dogs, and so the
distribution is quite lumpy. Each dog is counted as having the typical weight of
its breed. Very popular breeds therefore pile up on single values. Many ZIP
codes have a median of exactly 12.5 pounds, for example. (That’s the Shih Tzu’s
weight.)</p>
<p>Even so, we still see a clear pattern. The ZIP codes with the biggest dogs are
in lower Manhattan and brownstone Brooklyn. Park Slope is at the top, with a
median of 29 pounds. Woodlawn and City Island in the Bronx, and the Rockaways in
Queens, also have big dogs. The smallest dogs are in most of the Bronx,
southeast Queens, and East New York and Brownsville in Brooklyn. There, the
median dog weighs 12.5 pounds.</p>
<p>So Bill’s idea is partly supported by the evidence. Some of the city’s edges,
where people have more room on average, do have bigger dogs. But, on the other
hand, some of the densest apartment neighborhoods in the city have the biggest
dogs of all.</p>
<h2 id="size-classes">Size classes</h2>
<p>A median can hide a lot. I sorted the breeds into five size classes. I used the
weight bands that you see on bags of dog food: Toy (up to about 9 pounds), Small
(9 to 22), Medium (22 to 55), Large (55 to 99), and Giant (over 99). This is
kind of arbitrary, and not even arbitrary in the “AKC Standard” sense of
arbitrary. But I don’t think it’s unreasonable.</p>
<p>Across the city, here is how the dogs with a known size divide up:</p>
<ul>
<li>About a quarter are Toy dogs.</li>
<li>About a third are Small.</li>
<li>About a quarter are Medium.</li>
<li>14 percent are Large.</li>
<li>Fewer than 2 percent are Giant.</li>
</ul>
<p>Here is the share of each class within each ZIP code.</p>
<figure class="full-width"><a href="https://i2.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-classes-zip.png?ssl=1" rel="nofollow" target="_blank">
    <img src="https://i2.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-classes-zip.png?w=578&#038;ssl=1"
         alt="Share of dogs of each size class, by ZIP code." data-recalc-dims="1"/></a>
</figure>
<p>Toy dogs are most common in the Bronx and in parts of Brooklyn and Queens.
Medium dogs cluster in somewhat fancier parts of Brooklyn and lower Manhattan.
Large dogs are most common on Staten Island, in the Rockaways, and (again) in
parts of Manhattan. Giant dogs are rare everywhere.</p>
<p>The same numbers aggregated up to the level of boroughs are a little easier to
compare:</p>
<figure class="full-width"><a href="https://i0.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-classes-borough.png?ssl=1" rel="nofollow" target="_blank">
    <img src="https://i0.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-classes-borough.png?w=578&#038;ssl=1"
         alt="Heatmap of the percent of dogs in each size class, by borough." data-recalc-dims="1"/></a>
</figure>
<p>Small dogs are about a third of all licensed dogs in every borough. The
differences are at the ends. A third of the Bronx’s dogs are Toy dogs, compared
with about a fifth of Manhattan’s. Only 9 percent of the Bronx’s dogs are Large,
compared with 16 percent in Manhattan and 17 percent on Staten Island. Staten
Island, the least densely-populated borough, has the largest share of big dogs.</p>
<h2 id="mo-money-mo-doggo">Mo Money, Mo Doggo</h2>
<p>Manhattan and brownstone Brooklyn are dense, but they are also rich. The Bronx
is dense too, but much poorer. Happily, thanks to both <a href="https://www.nyc.gov/opendata" rel="nofollow" target="_blank">NYC OpenData</a> and the <a href="https://www.census.gov/" rel="nofollow" target="_blank">Census Bureau</a>, we have relevant housing and income measures available that we can construct for each ZIP code.</p>
<ul>
<li><em>Floor area per home.</em> This comes from
<a href="https://www.nyc.gov/site/planning/data-maps/open-data/dwn-pluto-mappluto.page" rel="nofollow" target="_blank">PLUTO</a>,
the city’s database of every tax lot. I added up the residential floor area
and the number of homes on every lot in each ZIP code, and divided. This is
gross floor area, so it includes halls and stairs. It’s a rough measure of
apartment size. You can use my <a href="https://kjhealy.github.io/nycpluto/" rel="nofollow" target="_blank"><code>nycpluto</code></a>
package to access these data in R.</li>
<li><em>Percent of homes in one- or two-family houses.</em> This is also from PLUTO,
using each lot’s building class.</li>
<li><em>Median rooms per home,</em> <em>percent of homes that are owner-occupied,</em> and
<em>median household income.</em> These come from the Census Bureau’s American
Community Survey, using the five-year estimates for 2020 to 2024. You can
query the Census API directly for these, or use my
<a href="https://kjhealy.github.io/nycdemog/" rel="nofollow" target="_blank"><code>nycdemog</code></a> package to get them.</li>
</ul>
<p>The Census reports these figures for ZCTAs, its version of ZIP code areas. Most
MODZCTAs contain only one ZCTA. Where a MODZCTA contains more than one, I added
up the counts. For the medians, I took an average of the ZCTA medians, weighted
by the number of households. The Census caps median income at $250,000. A few of
the richest ZIP codes in Manhattan hit that cap.</p>
<p>I fit a regression with this data where the outcome is the typical weight of the
dogs in each ZIP code. I use an average rather than the median here, because the
median is so lumpy. Specifically, I average the logged weights, which is the
same as taking the geometric mean. Breed weights are very skewed. The middle dog
weighs about 17 pounds, but a few breeds weigh well over 100. And differences
in weight are proportional. Five pounds is a big difference between two toy
dogs, but not between two retrievers. On the log scale, effects become percent
differences in weight. The predictors are the five measures above, plus the
borough. There are 177 ZIP codes in the model.</p>
<p>The five measures use different units: dollars, square feet, rooms, and
percentages. This makes the coefficients hard to compare directly. What we can
do is ask “If we take a ZIP code that is low on this measure (at the 25th
percentile of ZIP codes) and move it to a high value (the 75th percentile),
holding everything else constant, by what percent do its dogs get bigger or
smaller?” For the boroughs, the figure shows the difference between each borough and
Manhattan.</p>
<figure class="full-width"><a href="https://i2.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-effects.png?ssl=1" rel="nofollow" target="_blank">
    <img src="https://i2.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-effects.png?w=578&#038;ssl=1"
         alt="Dot-and-whisker plot of the estimated percent effects on typical dog size. Income has a clear positive effect of about 17 percent. Floor area per home has a small negative effect. Staten Island&#39;s dogs are about 10 percent heavier than Manhattan&#39;s. The other effects are close to zero." data-recalc-dims="1"/></a>
</figure>
<p>Income is the clearest result. A ZIP code with a median household income of
$116,000 has dogs that are about 17 percent heavier, on average, than one with a
median income of $67,000. The housing measures make much less difference once
we know income, and they do not point the way Bill expected. The share of homes
in one- or two-family houses and the share of owners have effects close to
zero. Median rooms has a small positive effect, maybe, with an interval that
includes zero. Floor area per home has a small but clearly negative effect.
Moving from about 910 to about 1,100 square feet per home goes with dogs about
3 percent <em>smaller</em>, at the same income. Together, the four housing measures
do improve the model a little, by more than we would expect by chance. But
mostly that is the floor area effect, and it runs the wrong way for the idea
that more room means bigger dogs.</p>
<p>Among the boroughs, Staten Island’s dogs are about 10 percent heavier than
Manhattan’s at the same levels of income and housing. That fits the idea of room
on the periphery, although the interval is wide, running from about 1 to 20
percent. The other boroughs do not differ much from Manhattan.</p>
<p>In terms of how much variation we can explain, income alone accounts for about
53 percent of the variation in typical dog size across ZIP codes. Adding the
borough raises that to 54 percent. Adding all four housing measures raises it
to 57 percent.</p>
<p>Here is the income effect on its own. This is a partial regression plot. For
both dog size and income, we remove the part that the borough and the housing
measures predict. What remains shows the relationship between income and dog
size among ZIP codes with similar housing in the same borough.</p>
<figure class="full-width"><a href="https://i0.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-income-net.png?ssl=1" rel="nofollow" target="_blank">
    <img src="https://i0.wp.com/kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/nyc-dog-size-income-net.png?w=578&#038;ssl=1"
         alt="Scatterplot of ZIP codes, colored by borough. The x-axis is median income relative to what borough and housing predict. The y-axis is typical dog size relative to the same prediction, as a percent difference. The points rise from left to right." data-recalc-dims="1"/></a>
</figure>
<p>The relationship is pretty good. Each doubling of median household income goes
with dogs about 22 percent heavier. The points come from every borough. So there
is a size gradient, as Bill speculated, but it’s not really geography at work
here, it’s money. Some of the biggest dogs in New York live in dense, expensive
neighborhoods such as Park Slope, SoHo, and the West Village. The smallest dogs
live in the Bronx, where incomes are much lower. Once we know a ZIP code’s
income, its housing tells us only a little more about its dogs, and not in the
expected direction. If anything, more floor area per home goes with slightly
smaller dogs. Staten Island is the one place where extra room may add to dog
size, beyond what income predicts.</p>
<p>Again, as a paid-up social science rather than infographic guy, I feel like I
have to close with the caveats. Remember these are <em>breed</em> weights, not dog
weights. Four in ten dogs have no breed size. And, like we said before, these
are the licensed dogs only. Licensing rates probably vary with income too.
Finally, this is a comparison of ZIP codes, not of households. It tells us that
richer ZIP codes have bigger dogs on average. It does not tell us that richer
people choose bigger dogs, although that seems likely.</p>
<p>Why would money matter more than space? Big dogs cost more to feed and to treat
at the vet. They are harder to keep in buildings that limit pets by weight. And
their owners may need to pay someone to walk them while they are at work.
Perhaps those many daily dog visits to Central Park are part of the answer too.
If you live near a very large park, maybe it’s easier to have a very nice
apartment that’s comparatively small for your very large dog.</p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://kieranhealy.org/blog/archives/2026/10/25/dogs-of-new-york-shapes-and-sizes-edition/"> R on kieranhealy.org</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/dogs-of-new-york-shapes-and-sizes-edition/">Dogs of New York, Shapes and Sizes Edition</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404440</post-id>	</item>
		<item>
		<title>Make Your Own LLM Agent in R</title>
		<link>https://www.r-bloggers.com/2026/10/make-your-own-llm-agent-in-r/</link>
		
		<dc:creator><![CDATA[Seascapemodels]]></dc:creator>
		<pubDate>Sun, 25 Oct 2026 16:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://www.seascapemodels.org/posts/2026-10-26-make-your-own-agent-in-r/</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; ">
<p>It’s really easy to make your own agent. You should do it once, just to understand how this technology works.<br />
LLM agents are becoming pervasive, with Meta’s Muse perhaps the first consumer grade agent. They are only going to get more common. If ...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/make-your-own-llm-agent-in-r/">Make Your Own LLM Agent in R</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://www.seascapemodels.org/posts/2026-10-26-make-your-own-agent-in-r/"> Seascapemodels</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
 





<p>It’s really easy to make your own agent. You should do it once, just to understand how this technology works.</p>
<p>LLM agents are becoming pervasive, with Meta’s Muse perhaps the first consumer grade agent. They are only going to get more common. If you know a bit of R you can program your own, and it makes the whole thing feel a lot less like magic.</p>
<p>I’ve updated my live book with <a href="https://www.seascapemodels.org/AI-assistants-for-scientific-coding/section-7-api-access/05-make-an-agent.html" rel="nofollow" target="_blank">a new section on making your own agent with R</a>.</p>
<p>The tutorial runs you through using the <code>ellmer</code> package to connect to an LLM. Then you create tools. These are functions that let the agent take actions, like writing and running R code. You tell the agent how to use the functions, then write a parser that checks the LLM response for tool use and executes the tools if it finds any. Then you put all that in a <code>while</code> loop so the agent re-consumes the output of the code it wrote.</p>
<p>That’s it.</p>
<p>This all runs more smoothly if you ‘register’ tools formally, because the agent is less likely to make syntax mistakes when using them. <code>ellmer</code> supports this.</p>
<section id="turbochook" class="level2">
<h2 class="anchored" data-anchor-id="turbochook">turbochook</h2>
<p>To illustrate the registered tools approach I made an R based agent (with Claude Code) in a package called <code>turbochook</code>. <a href="https://github.com/cbrown5/turbochook" rel="nofollow" target="_blank">You can install it from github</a>.</p>
<p>There are many <a href="https://luisdva.github.io/llmsr-book/r-pkgs.html#language-model-agents-and-ai-assistants" rel="nofollow" target="_blank">R based agents out there already</a>. I wanted my own that was simple enough for experimentation and learning.</p>
<p>There are three main functions:</p>
<ul>
<li><code>handfish()</code> is for beginners and the security conscious. It is simpler and requires manual approval at every step.</li>
<li><code>turbochook()</code> runs an agentic loop autonomously. It won’t ask for permission, it just acts.</li>
<li><code>ralph()</code> runs a ralph loop (basically a self completing todo list).</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://i1.wp.com/www.seascapemodels.org/posts/2026-10-26-make-your-own-agent-in-r/turbchook-in-action.png?w=578&#038;ssl=1" class="img-fluid figure-img" data-recalc-dims="1"></p>
<figcaption>turbochook in action in my R console</figcaption>
</figure>
</div>
<p><em>turbochook in action in my R console</em></p>
<p>This is very much alpha stage software. Like all agents, there is no cybersecurity guarantee with these tools, so use them with caution. It’s best to run them in a sandbox or isolated container.</p>
<p>What I wanted was a simple and transparent agent. They all print progress as they go, including tool use and tokens used, so you can see exactly what the agent is doing. Everything is written in R, so R users will find it easier to interpret than commercial agents like Claude Code, which often write in unix or python.</p>
<p>If you give it a go, send me feedback here or via the github repo.</p>


</section>

 
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://www.seascapemodels.org/posts/2026-10-26-make-your-own-agent-in-r/"> Seascapemodels</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/make-your-own-llm-agent-in-r/">Make Your Own LLM Agent in R</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404438</post-id>	</item>
		<item>
		<title>{poorman}: Tips and Tricks for Faster Grouped Operations in base R</title>
		<link>https://www.r-bloggers.com/2026/10/poorman-tips-and-tricks-for-faster-grouped-operations-in-base-r/</link>
		
		<dc:creator><![CDATA[Random R Ramblings]]></dc:creator>
		<pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">/2026/10/25/poorman-tips-and-tricks-for-faster-grouped-operations-in-base-r/</guid>

					<description><![CDATA[<p>Introduction<br />
When learning R, we are always told to vectorise, vectorise, vectorise. This is because R is an interpreted language.<br />
Every time R runs a line of R code, it has to look up each name, match the arguments of each function call and set up...</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/poorman-tips-and-tricks-for-faster-grouped-operations-in-base-r/">{poorman}: Tips and Tricks for Faster Grouped Operations in base R</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://nathaneastwood.github.io/2026/10/25/poorman-tips-and-tricks-for-faster-grouped-operations-in-base-r/"> Random R Ramblings</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>



<div id="introduction" class="section level1">
<h1>Introduction</h1>
<p>When learning R, we are always told to vectorise, vectorise, vectorise. This is because R is an interpreted language.
Every time R runs a line of R code, it has to look up each name, match the arguments of each function call and set up
a new environment for each function it enters. A vectorised function such as <code>*</code>, <code>cumsum()</code> or <code>match()</code> does that
once and then loops over the elements in compiled code, where each step is very cheap. A <code>for</code> loop written in R does
it on every iteration.</p>
<pre>set.seed(42)
x &lt;- runif(1e5)
loop &lt;- function(x) {
  out &lt;- numeric(length(x))
  for (i in seq_along(x)) out[i] &lt;- x[i] * 2
  out
}
bench::mark(loop = loop(x), vectorised = x * 2)[, c(&quot;expression&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 2 x 3
#   expression   median mem_alloc
#   &lt;bch:expr&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 loop         2.22ms     819KB
# 2 vectorised  59.02us     781KB</pre>
<p>The loop gives the same answer and uses the same memory, but it is nearly 40 times slower. Bear in mind that
<code>lapply()</code>, <code>vapply()</code> and friends are loops too. They tidy the loop away, but each iteration still calls an R
function, so what matters is how many times we go round and how much work we do each time.</p>
<p>Grouped operations make this advice tricky to follow. <code>mutate(df, y = x - mean(x))</code> on grouped data means “evaluate
<code>x - mean(x)</code> for each group”, and since the expression can be any R code at all, there is no way around evaluating it
once per group. What we can do is make everything around that loop vectorised, and keep the work inside it to a
minimum.</p>
<p>{poorman} has not always done this. When I <a href="https://nathaneastwood.github.io/2020/02/27/building-a-base-dplyr-with-primitives-grouped-operations-pipes-and-more/" rel="nofollow" target="_blank">first wrote about grouped operations in
{poorman}</a>,
they used the classic split-apply-combine approach: split the <code>data.frame</code> into one <code>data.frame</code> per group, run the
verb on each piece and <code>rbind()</code> the results back together. It was simple, but it was slow once there were more than a
few hundred groups. I have since rewritten the grouped verbs, and in this post I want to share the tricks that made the
difference. All of them are plain base R, so you can use them in your own code too.</p>
</div>
<div id="split-apply-combine-versus-vectorised" class="section level1">
<h1>Split-Apply-Combine Versus Vectorised</h1>
<p>To keep things concrete, let’s centre a column within groups, which is what <code>mutate(x = x - mean(x))</code> does on grouped
data. The split-apply-combine version is how most of us would first write it.</p>
<pre>centre_split &lt;- function(df) {
  df$row &lt;- seq_len(nrow(df))
  pieces &lt;- split(df, df$g)
  pieces &lt;- lapply(pieces, function(piece) {
    piece$centred &lt;- piece$x - mean(piece$x)
    piece
  })
  res &lt;- do.call(rbind, pieces)
  res$centred[order(res$row)]
}</pre>
<p>And here is the version {poorman} has ended up with, stripped down to the bare bones.</p>
<pre>centre_fast &lt;- function(df) {
  ids &lt;- match(df$g, unique(df$g))
  f &lt;- structure(ids, levels = as.character(seq_len(max(ids))), class = &quot;factor&quot;)
  pieces &lt;- split.default(df$x, f)
  res &lt;- unlist(lapply(pieces, function(x) x - mean(x)), use.names = FALSE)
  rows &lt;- unlist(split.default(seq_along(ids), f), use.names = FALSE)
  back &lt;- integer(length(rows))
  back[rows] &lt;- seq_along(rows)
  res[back]
}</pre>
<p>Both still have an <code>lapply()</code> over the groups. The difference is what else happens. <code>centre_split()</code> builds a
<code>data.frame</code> for every group, adds a column to each one and then binds them all back together. <code>centre_fast()</code> only
splits the one column it needs, does nothing in the loop except the calculation itself, and combines the results with
a single <code>unlist()</code>.</p>
<p>To time them, I use <code>bench::press()</code>, which runs <code>bench::mark()</code> once for each value of <code>n_groups</code>, on 100,000 rows.
<code>bench::mark()</code> also checks that both functions return the same result.</p>
<pre>res &lt;- bench::press(
  n_groups = c(10, 1e3, 1e4),
  {
    set.seed(42)
    df &lt;- data.frame(g = sample(n_groups, 1e5, replace = TRUE), x = runif(1e5))
    bench::mark(split = centre_split(df), fast = centre_fast(df), iterations = 10L)
  },
  .quiet = TRUE
)
res[, c(&quot;expression&quot;, &quot;n_groups&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 6 x 4
#   expression n_groups   median mem_alloc
#   &lt;bch:expr&gt;    &lt;dbl&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 split            10  29.62ms    26.1MB
# 2 fast             10   3.76ms    7.57MB
# 3 split          1000  86.71ms   61.97MB
# 4 fast           1000   6.23ms    7.67MB
# 5 split         10000 522.85ms   55.61MB
# 6 fast          10000  26.27ms    6.18MB</pre>
<p><code>centre_fast()</code> is about 8 times faster with 10 groups, 14 times faster with 1,000 groups and 20 times faster with
10,000 groups, and it uses far less memory. Let’s go through the tricks one at a time. For the rest of the post, I’ll
use this data for the timings, with 1,000 groups:</p>
<pre>set.seed(42)
n &lt;- 1e5
df &lt;- data.frame(g = sample(1e3, n, replace = TRUE), x = runif(n))
g &lt;- df$g</pre>
<p>and this small dataset to show what each trick does.</p>
<pre>sales &lt;- data.frame(
  shop = c(&quot;b&quot;, &quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;a&quot;, &quot;b&quot;),
  day = c(1L, 1L, 2L, 1L, 1L, 2L),
  amount = c(2, 4, 6, 8, 10, 12)
)
sales
#   shop day amount
# 1    b   1      2
# 2    a   1      4
# 3    b   2      6
# 4    c   1      8
# 5    a   1     10
# 6    b   2     12</pre>
</div>
<div id="tip-1-number-the-groups-with-match" class="section level1">
<h1>Tip 1: Number the Groups With <code>match()</code></h1>
<p>The first job is to work out which rows belong to which group. The obvious way is to compare every row with every
group.</p>
<pre>lapply(unique(sales$shop), function(shop) which(sales$shop == shop))
# [[1]]
# [1] 1 3 6
# 
# [[2]]
# [1] 2 5
# 
# [[3]]
# [1] 4</pre>
<p>That is <code>n * G</code> comparisons for <code>n</code> rows and <code>G</code> groups, and each comparison allocates a new logical vector the length
of the data. It is much faster to give each row a group number in one go with <code>match()</code>.</p>
<pre>match(sales$shop, unique(sales$shop))
# [1] 1 2 1 3 2 1</pre>
<p>Each value gets the position where it first appears in <code>unique(sales$shop)</code>, so the groups are numbered in the order
they appear. Both <code>unique()</code> and <code>match()</code> hand their work to C code inside R itself (you can see the call to
<code>.Internal()</code> if you print <code>match</code>), and that code uses a <em>hash table</em>. A hash table is a lookup table where each value
is turned into a number, its hash, which says where in the table the value is stored. Looking a value up then means
working out its hash and checking that one place, rather than searching the whole table, so it takes about the same
time however many values are stored. <code>unique()</code> goes through the vector once and adds each value it hasn’t seen
before to a table; <code>match()</code> puts the values of <code>unique(sales$shop)</code> in a table and looks up each element of
<code>sales$shop</code> in it. Either way, each row is looked at once, whatever the number of groups.</p>
<pre>bench::mark(
  compare = lapply(unique(g), function(value) which(g == value)),
  match = match(g, unique(g)),
  check = FALSE
)[, c(&quot;expression&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 2 x 3
#   expression   median mem_alloc
#   &lt;bch:expr&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 compare       162ms  764.87MB
# 2 match         653us    2.16MB</pre>
<p>(<code>check = FALSE</code> is needed here because the two give their answers in different forms; Tip 2 turns the group numbers
into the rows of each group.)</p>
<p>With more than one grouping column, we need a number for each <em>combination</em> of values. The original version of
{poorman} used base R’s <code>interaction()</code> for this, which builds a label for each row by pasting the values together
with a <code>&quot;.&quot;</code>. Unfortunately, pasting can give two different groups the same label.</p>
<pre>interaction(c(&quot;a.b&quot;, &quot;a&quot;), c(&quot;c&quot;, &quot;b.c&quot;))
# [1] a.b.c a.b.c
# Levels: a.b.c a.b.b.c a.c</pre>
<p><code>(&quot;a.b&quot;, &quot;c&quot;)</code> and <code>(&quot;a&quot;, &quot;b.c&quot;)</code> are different groups, but both become <code>a.b.c</code>. <code>interaction()</code> also makes a level
for every possible combination of values, whether or not it appears in the data, which can use a lot of memory with
several columns. Instead, I number each column with <code>match()</code> and combine the numbers with a little arithmetic, a bit
like turning two digits into one number.</p>
<pre>shop_id &lt;- match(sales$shop, unique(sales$shop))
day_id &lt;- match(sales$day, unique(sales$day))
combined &lt;- (shop_id - 1) * max(day_id) + day_id
combined
# [1] 1 3 2 5 3 2
match(combined, unique(combined))
# [1] 1 2 3 4 2 3</pre>
<p>Each <code>(shop, day)</code> pair gets its own number, so no two groups can collide. Calling <code>match()</code> again on the combined
number brings the ids back down to <code>1, 2, ..., G</code>, so they stay small when there are more columns to add. The <code>- 1</code> is
a double, so the arithmetic is done in doubles and can’t overflow R’s integers.</p>
</div>
<div id="tip-2-build-factors-by-hand" class="section level1">
<h1>Tip 2: Build Factors by Hand</h1>
<p>Given the group ids, <code>split()</code> gives us the rows of each group. But <code>split()</code> wants a factor, and calling <code>factor(ids)</code>
would sort the unique values and <code>match()</code> every value against them again: work we have already done. The ids are
already the integer codes <code>1:G</code>, so I attach the <code>levels</code> and <code>class</code> attributes directly with <code>structure()</code> and have a
valid factor at no cost.</p>
<pre>ids &lt;- match(combined, unique(combined))
f &lt;- structure(ids, levels = as.character(seq_len(max(ids))), class = &quot;factor&quot;)
f
# [1] 1 2 3 4 2 3
# Levels: 1 2 3 4
split.default(seq_along(ids), f)
# $`1`
# [1] 1
# 
# $`2`
# [1] 2 5
# 
# $`3`
# [1] 3 6
# 
# $`4`
# [1] 4</pre>
<p>On 100,000 rows with 1,000 groups, this is 4 times faster than going through <code>factor()</code>.</p>
<pre>ids &lt;- match(g, unique(g))
bench::mark(
  factor = split(seq_along(ids), factor(ids)),
  structure = split(seq_along(ids), structure(ids, levels = as.character(seq_len(max(ids))), class = &quot;factor&quot;))
)[, c(&quot;expression&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 2 x 3
#   expression   median mem_alloc
#   &lt;bch:expr&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 factor       5.63ms     4.2MB
# 2 structure    1.28ms     1.2MB</pre>
<p>This only works because the ids are whole numbers from 1 to <code>G</code> with no gaps, which <code>match()</code> guarantees.</p>
</div>
<div id="tip-3-split-columns-not-data.frames" class="section level1">
<h1>Tip 3: Split Columns, Not <code>data.frame</code>s</h1>
<p><code>split()</code> on a <code>data.frame</code> calls <code>[.data.frame</code> once for each group. <code>[.data.frame</code> is a long R function that has to
handle every way of indexing a <code>data.frame</code> and checks the row names every time. Splitting each column on its own
avoids all of that.</p>
<pre>f &lt;- structure(ids, levels = as.character(seq_len(max(ids))), class = &quot;factor&quot;)
bench::mark(
  data_frame = split(df, f),
  columns = lapply(df, split.default, f),
  check = FALSE
)[, c(&quot;expression&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 2 x 3
#   expression   median mem_alloc
#   &lt;bch:expr&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 data_frame  13.01ms     3.9MB
# 2 columns      2.04ms    2.02MB</pre>
<p>There is a second choice to make here. If we already have the rows of each group, we can also split a column by
indexing it once per group, <code>lapply(rows, function(i) x[i])</code>. Which is faster depends on the size of the groups.</p>
<pre>res &lt;- bench::press(
  n_groups = c(100, 1e3, 3e3, 1e4),
  {
    set.seed(42)
    x &lt;- runif(1e5)
    ids &lt;- match(sample(n_groups, 1e5, replace = TRUE), seq_len(n_groups))
    f &lt;- structure(ids, levels = as.character(seq_len(n_groups)), class = &quot;factor&quot;)
    rows &lt;- split.default(seq_along(x), f)
    bench::mark(index = lapply(rows, function(i) x[i]), split = split.default(x, f), check = FALSE)
  },
  .quiet = TRUE
)
res[, c(&quot;expression&quot;, &quot;n_groups&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 8 x 4
#   expression n_groups   median mem_alloc
#   &lt;bch:expr&gt;    &lt;dbl&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 index           100 232.35us  786.77KB
# 2 split           100 964.07us    1.15MB
# 3 index          1000 526.28us  835.98KB
# 4 split          1000   1.02ms     1.2MB
# 5 index          3000    1.1ms  944.86KB
# 6 split          3000    1.1ms    1.32MB
# 7 index         10000    2.9ms  131.44KB
# 8 split         10000   1.18ms  561.22KB</pre>
<p><code>split.default()</code> costs about 1 ms whatever the number of groups, because it always goes through the whole column.
Indexing costs one R function call per group, so it is cheap with a few large groups and expensive with many small
ones. The two meet at about 3,000 groups, which is about 33 rows per group, so {poorman} indexes when the groups have
32 rows or more on average and uses <code>split.default()</code> otherwise.</p>
</div>
<div id="tip-4-combine-with-one-unlist" class="section level1">
<h1>Tip 4: Combine With One <code>unlist()</code></h1>
<p><code>do.call(rbind, pieces)</code> has the same problem as splitting a <code>data.frame</code>, but in reverse. <code>rbind.data.frame()</code> has to
cope with columns in a different order, factors with different levels, duplicated row names and more, and it does
those checks for every piece. If each group gives a plain vector, <code>unlist()</code> combines them in one call.</p>
<pre>pieces_df &lt;- lapply(split(df, f), function(piece) data.frame(m = mean(piece$x)))
pieces_vec &lt;- lapply(split.default(df$x, f), mean)
bench::mark(
  rbind = do.call(rbind, pieces_df)$m,
  unlist = unlist(pieces_vec, use.names = FALSE)
)[, c(&quot;expression&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 2 x 3
#   expression   median mem_alloc
#   &lt;bch:expr&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 rbind        7.11ms  267.74KB
# 2 unlist       7.38us    7.86KB</pre>
<p>With 1,000 one row pieces, <code>unlist()</code> is about 1,000 times faster. Of course <code>unlist()</code> drops attributes, so factors
and dates need a little extra care, but for plain numbers and characters this is all it takes.</p>
</div>
<div id="tip-5-put-the-rows-back-with-one-permutation" class="section level1">
<h1>Tip 5: Put the Rows Back With One Permutation</h1>
<p>After the combine, the results are in group order: all of group 1, then all of group 2 and so on. <code>mutate()</code> has to
return them in the original row order. <code>unlist(rows)</code> tells us which row each result belongs to, and writing
<code>seq_along(rows)</code> into those positions turns that around, giving the position of each row’s result.</p>
<pre>rows &lt;- unlist(split.default(seq_along(shop_id), shop_id), use.names = FALSE)
rows
# [1] 1 3 6 2 5 4
back &lt;- integer(length(rows))
back[rows] &lt;- seq_along(rows)
back
# [1] 1 4 2 6 5 3</pre>
<p><code>match()</code> numbered shop <code>b</code> first, so group order is the three <code>b</code> rows, then the two <code>a</code> rows, then <code>c</code>. Row 1 is
shop <code>b</code>, so its result is 1st in group order; row 2 is shop <code>a</code>, so its result is 4th, after the three results for
<code>b</code>; and so on. Now any result can be put back with a single subset.</p>
<pre>totals &lt;- unlist(lapply(split(sales$amount, shop_id), sum), use.names = FALSE)
by_group &lt;- rep(totals, tabulate(shop_id))
by_group[back]
# [1] 20 14 20  8 14 20</pre>
<p><code>back</code> only has to be worked out once, and since it is used with <code>[</code>, it works for every new column whatever its type.</p>
</div>
<div id="tip-6-remove-the-loop-when-you-can" class="section level1">
<h1>Tip 6: Remove the Loop When You Can</h1>
<p>Some grouped calculations do not need a loop over the groups at all. If you know the function in advance, base R often
has a grouped version already. The mean from our example can be done with <code>rowsum()</code>, which sums each group in C.</p>
<pre>centre_rowsum &lt;- function(df) {
  ids &lt;- match(df$g, unique(df$g))
  df$x - (rowsum(df$x, ids, reorder = FALSE) / tabulate(ids))[ids]
}
res &lt;- bench::press(
  n_groups = c(10, 1e3, 1e4),
  {
    set.seed(42)
    df &lt;- data.frame(g = sample(n_groups, 1e5, replace = TRUE), x = runif(1e5))
    bench::mark(fast = centre_fast(df), rowsum = centre_rowsum(df), iterations = 10L)
  },
  .quiet = TRUE
)
res[, c(&quot;expression&quot;, &quot;n_groups&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 6 x 4
#   expression n_groups   median mem_alloc
#   &lt;bch:expr&gt;    &lt;dbl&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 fast             10   3.81ms    7.49MB
# 2 rowsum           10   1.64ms    4.68MB
# 3 fast           1000   5.55ms    7.67MB
# 4 rowsum         1000   1.64ms    4.71MB
# 5 fast          10000  22.65ms    6.18MB
# 6 rowsum        10000   6.59ms    5.15MB</pre>
<p>{poorman} can’t do this for <code>mutate()</code>, because it doesn’t know what the expression will be, but it can for verbs that
don’t take an expression to evaluate for each group.</p>
<p>My favourite example is filling missing values, as in <code>fill()</code>. Filling down means replacing each <code>NA</code> with the last
value before it that isn’t missing. Take the position of each element, set it to 0 where the value is missing, and
take the running maximum with <code>cummax()</code>. That gives the position of the last value that isn’t missing.</p>
<pre>x &lt;- c(NA, 3, NA, NA, 5, NA)
seq_along(x) * !is.na(x)
# [1] 0 2 0 0 5 0
cummax(seq_along(x) * !is.na(x))
# [1] 0 2 2 2 5 5</pre>
<p>The first element has no value before it, so it gets 0. To do this within groups, put the rows in group order and
always count the first element of each group, missing or not. That fixes the 0, and it also stops a value leaking
into the next group.</p>
<pre>first &lt;- c(TRUE, FALSE, FALSE, TRUE, FALSE, FALSE)  # two groups: positions 1-3 and 4-6
from &lt;- cummax(seq_along(x) * (!is.na(x) | first))
from
# [1] 1 2 2 4 5 5
x[from]
# [1] NA  3  3 NA  5  5</pre>
<p>Position 4 stays <code>NA</code> rather than taking the 3 from the first group. Here is a version that splits the data and uses
the trick inside each group, and a version that uses it once on the whole column.</p>
<pre>fill_one &lt;- function(x) {
  pos &lt;- seq_along(x)
  x[cummax(pos * (!is.na(x) | pos == 1L))]
}
fill_split &lt;- function(x, ids) {
  f &lt;- structure(ids, levels = as.character(seq_len(max(ids))), class = &quot;factor&quot;)
  unsplit(lapply(split(x, f), fill_one), f)
}
fill_whole &lt;- function(x, ids) {
  idx &lt;- order(ids)
  sorted &lt;- ids[idx]
  first &lt;- c(TRUE, sorted[-1L] != sorted[-length(sorted)])
  y &lt;- x[idx]
  out &lt;- x
  out[idx] &lt;- y[cummax(seq_along(y) * (!is.na(y) | first))]
  out
}
res &lt;- bench::press(
  n_groups = c(10, 1e3, 1e4),
  {
    set.seed(42)
    ids &lt;- match(sample(n_groups, 1e5, replace = TRUE), seq_len(n_groups))
    z &lt;- ifelse(runif(1e5) &lt; 0.3, NA, runif(1e5))
    bench::mark(split = fill_split(z, ids), whole = fill_whole(z, ids), iterations = 10L)
  },
  .quiet = TRUE
)
res[, c(&quot;expression&quot;, &quot;n_groups&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]
# # A tibble: 6 x 4
#   expression n_groups   median mem_alloc
#   &lt;bch:expr&gt;    &lt;dbl&gt; &lt;bch:tm&gt; &lt;bch:byt&gt;
# 1 split            10   3.64ms    7.69MB
# 2 whole            10   1.96ms    9.16MB
# 3 split          1000   4.91ms    8.13MB
# 4 whole          1000   2.07ms    9.16MB
# 5 split         10000  11.53ms    3.45MB
# 6 whole         10000   2.09ms    9.16MB</pre>
<p>Both functions use the same <code>cummax()</code> trick, so the only difference is the loop over the groups. With 10 groups
that loop costs little, but with 10,000 groups <code>fill_whole()</code> is 5 times faster. It uses a little more memory, for the
reordered copy of the column.</p>
</div>
<div id="how-poorman-puts-it-together" class="section level1">
<h1>How {poorman} Puts It Together</h1>
<p>So far these have been small base R functions. Here is how {poorman} uses the same tricks in its verbs.</p>
<div id="grouped-data" class="section level2">
<h2>Grouped data</h2>
<p><code>group_by()</code> works out the groups once and stores them on the data as an attribute. <code>group_data()</code> shows them: one row
per group, with the values of the grouping columns and a list column, <code>.rows</code>, of the rows in each group.</p>
<pre>library(poorman)
gs &lt;- group_by(sales, shop)
group_data(gs)
#   shop   .rows
# 1    a    2, 5
# 2    b 1, 3, 6
# 3    c       4</pre>
<p>To build this, <code>group_by()</code> numbers the rows with <code>match()</code> as in Tip 1 and splits the row numbers with a hand built
factor as in Tip 2. The groups are then sorted by their values, which is why <code>a</code> comes first here. Every grouped verb
reads <code>.rows</code> rather than finding the groups again.</p>
</div>
<div id="evaluating-an-expression-for-each-group" class="section level2">
<h2>Evaluating an expression for each group</h2>
<p>Take <code>mutate(gs, share = amount / sum(amount))</code>. For each group, R has to evaluate <code>amount / sum(amount)</code> with
<code>amount</code> meaning the amounts of that group only. The base R tool for this is an environment: <code>eval(expr, env)</code>
evaluates <code>expr</code> and looks up its variables in <code>env</code> first. So for each group, {poorman} makes a small environment
holding that group’s piece of each column, and evaluates the expression in it. Here is that process written out by
hand for our example.</p>
<pre>expr &lt;- quote(amount / sum(amount))
rows &lt;- group_data(gs)$.rows
pieces &lt;- lapply(rows, function(i) sales$amount[i])
envs &lt;- lapply(pieces, function(amount) list2env(list(amount = amount), envir = new.env(parent = globalenv())))
results &lt;- lapply(envs, function(env) eval(expr, env))
results
# [[1]]
# [1] 0.2857143 0.7142857
# 
# [[2]]
# [1] 0.1 0.3 0.6
# 
# [[3]]
# [1] 1</pre>
<p>The parent of each environment is the environment the verb was called from, here the global environment. A variable
that isn’t a column, such as a <code>total</code> defined in your own function, isn’t found in the group’s environment, so R
looks for it in the parent, just as it would outside of {poorman}.</p>
<p>The results are then combined with one <code>unlist()</code> (Tip 4) and put back in row order with one permutation (Tip 5).</p>
<pre>idx &lt;- unlist(rows)
back &lt;- integer(length(idx))
back[idx] &lt;- seq_along(idx)
unlist(results)[back]
# [1] 0.1000000 0.2857143 0.3000000 1.0000000 0.7142857 0.6000000
mutate(gs, share = amount / sum(amount))$share
# [1] 0.1000000 0.2857143 0.3000000 1.0000000 0.7142857 0.6000000</pre>
<p>That is <code>mutate()</code> in miniature. The real thing splits every column, not just <code>amount</code>, using whichever of the two
methods in Tip 3 suits the size of the groups, and it checks that each result has the right size and type, but the
steps are the same. <code>summarise()</code>, <code>mutate()</code>, <code>transmute()</code>, <code>filter()</code> and <code>slice()</code> all share one internal
function that does this. Making one environment per group and evaluating the expressions in it is almost all of the
work it does for each group; everything else is done once for all groups.</p>
</div>
<div id="building-only-what-is-used" class="section level2">
<h2>Building only what is used</h2>
<p>Like {dplyr}, {poorman} lets you refer to the columns of the current group through <code>.data</code>. This is useful when the
name of a column is stored in a variable.</p>
<pre>var &lt;- &quot;amount&quot;
mutate(gs, double = .data[[var]] * 2)
#   shop day amount double
# 1    b   1      2      4
# 2    a   1      4      8
# 3    b   2      6     12
# 4    c   1      8     16
# 5    a   1     10     20
# 6    b   2     12     24</pre>
<p>For this to work, every group’s environment needs a variable called <code>.data</code> holding that group’s columns as a
<code>data.frame</code>. Building a <code>data.frame</code> for every group is exactly the cost that we have been trying to avoid, and most
expressions never use <code>.data</code>. So {poorman} only builds it when an expression reads it, using an <em>active binding</em>.
<code>makeActiveBinding()</code> creates a variable that holds a function rather than a value, and runs that function each time
the variable is read. Here is an environment for one group, where <code>.data</code> prints a message each time it is built.</p>
<pre>env &lt;- list2env(list(amount = c(4, 10)), envir = new.env())
makeActiveBinding(&quot;.data&quot;, function() {
  cat(&quot;Building .data\n&quot;)
  data.frame(amount = env$amount)
}, env)
eval(quote(sum(amount)), env)
# [1] 14
eval(quote(sum(.data[[&quot;amount&quot;]])), env)
# Building .data
# [1] 14</pre>
<p>The first expression never reads <code>.data</code>, so the <code>data.frame</code> is never built. The second reads it, and only then is it
built.</p>
</div>
<div id="subsetting-once-in-filter" class="section level2">
<h2>Subsetting once in <code>filter()</code></h2>
<p><code>filter()</code> evaluates its condition in each group’s environment in the same way, which gives a logical vector for each
group. These are combined and put back in row order exactly like the results of <code>mutate()</code>, which gives one logical
vector, <code>keep</code>, for all of the rows. The data is then subset once with it. The old approach subset each group’s
<code>data.frame</code> and bound the pieces back together.</p>
<p>The result is still grouped, and its groups don’t need to be found again. Filtering doesn’t change the values of the
grouping columns, it only removes rows, so only the row numbers in <code>.rows</code> change. The new number of each row that is
kept is <code>cumsum(keep)</code> at that row. Take the condition <code>amount &gt; 4</code>.</p>
<pre>keep &lt;- sales$amount &gt; 4
keep
# [1] FALSE FALSE  TRUE  TRUE  TRUE  TRUE
cumsum(keep)
# [1] 0 0 1 2 3 4
group_data(filter(gs, amount &gt; 4))
#   shop .rows
# 1    a     3
# 2    b  1, 4
# 3    c     2</pre>
<p>Shop <code>b</code> had rows 1, 3 and 6. Row 1 is dropped, and rows 3 and 6 become <code>cumsum(keep)[c(3, 6)]</code>, that is rows 1 and 4
of the result. {poorman} does this for all of the groups with one subset of <code>cumsum(keep)</code>.</p>
</div>
<div id="verbs-that-do-less-per-group" class="section level2">
<h2>Verbs that do less per group</h2>
<p><code>fill()</code> fills missing values within each group. It doesn’t evaluate an expression per group, so it doesn’t use the
environments at all. It puts the rows in group order with <code>unlist(group_data(gs)$.rows)</code>, marks the first row of each
group, and applies the <code>cummax()</code> trick from Tip 6 to the whole column, just as <code>fill_whole()</code> does.</p>
<p><code>distinct()</code> evaluates its arguments like <code>mutate()</code>, so that <code>distinct(gs, d = day + 1)</code> works, but it finds the
distinct rows without a loop over groups. The distinct values of <code>day</code> within each <code>shop</code> are just the distinct
combinations of <code>shop</code> and <code>day</code>, so it numbers those combinations as in Tip 1 and keeps the rows where
<code>duplicated()</code> is <code>FALSE</code>. With the <code>ids</code> for <code>shop</code> and <code>day</code> from Tip 2:</p>
<pre>which(!duplicated(ids))
# [1] 1 2 3 4
distinct(gs, day)
#   shop day
# 1    b   1
# 2    a   1
# 3    b   2
# 4    c   1</pre>
<p><code>slice_head()</code> still loops over the groups, but only to work out the positions of the first <code>n</code> rows from <code>.rows</code>.
That loop only handles short integer vectors, never the data or an environment, and the data is then subset once.</p>
<pre>unlist(lapply(group_data(gs)$.rows, head, 2))
# [1] 2 5 1 3 4
slice_head(gs, n = 2)
#   shop day amount
# 2    a   1      4
# 5    a   1     10
# 1    b   1      2
# 3    b   2      6
# 4    c   1      8</pre>
</div>
</div>
<div id="the-results" class="section level1">
<h1>The Results</h1>
<p>Here is the code I used to compare {poorman} 0.3.0, the version with these changes, with the current CRAN release,
0.2.8. I ran it twice, in separate R sessions, once with each version installed (<code>install.packages(&quot;poorman&quot;)</code> for
0.2.8 and <code>remotes::install_github(&quot;nathaneastwood/poorman&quot;)</code> for 0.3.0). <code>group_by()</code> times making the
grouped data; every other line times a grouped operation on <code>gdf</code>, the data grouped by <code>g</code>.</p>
<pre>library(poorman)
res &lt;- bench::press(
  n_groups = c(10, 1e3, 1e4),
  {
    set.seed(42)
    n &lt;- 1e5
    df &lt;- data.frame(
      g = sample(n_groups, n, replace = TRUE),
      x = runif(n),
      y = sample(5L, n, replace = TRUE)
    )
    df$z &lt;- ifelse(runif(n) &lt; 0.3, NA, df$x)
    gdf &lt;- group_by(df, g)
    bench::mark(
      group_by = group_by(df, g),
      summarise = summarise(gdf, m = mean(x)),
      mutate = mutate(gdf, m = x - mean(x)),
      filter = filter(gdf, x &gt; mean(x)),
      slice_head = slice_head(gdf, n = 2),
      distinct = distinct(gdf, y),
      fill = fill(gdf, z),
      iterations = 3L,
      check = FALSE
    )
  },
  .quiet = TRUE
)
res[, c(&quot;expression&quot;, &quot;n_groups&quot;, &quot;median&quot;, &quot;mem_alloc&quot;)]</pre>
<table>
<colgroup>
<col width="20%" />
<col width="10%" />
<col width="16%" />
<col width="16%" />
<col width="18%" />
<col width="18%" />
</colgroup>
<thead>
<tr>
<th>Verb</th>
<th align="right">Groups</th>
<th align="right">Time, 0.2.8</th>
<th align="right">Time, 0.3.0</th>
<th align="right">Memory, 0.2.8</th>
<th align="right">Memory, 0.3.0</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>group_by()</code></td>
<td align="right">10</td>
<td align="right">15.2 ms</td>
<td align="right">2.66 ms</td>
<td align="right">25.27 MB</td>
<td align="right">5.82 MB</td>
</tr>
<tr>
<td></td>
<td align="right">1,000</td>
<td align="right">1.53 s</td>
<td align="right">2.87 ms</td>
<td align="right">1.87 GB</td>
<td align="right">5.94 MB</td>
</tr>
<tr>
<td></td>
<td align="right">10,000</td>
<td align="right">15.2 s</td>
<td align="right">5.35 ms</td>
<td align="right">18.64 GB</td>
<td align="right">5.83 MB</td>
</tr>
<tr>
<td><code>summarise()</code></td>
<td align="right">10</td>
<td align="right">7.55 ms</td>
<td align="right">2.08 ms</td>
<td align="right">13.71 MB</td>
<td align="right">5 MB</td>
</tr>
<tr>
<td></td>
<td align="right">1,000</td>
<td align="right">174.84 ms</td>
<td align="right">11.18 ms</td>
<td align="right">33.49 MB</td>
<td align="right">4.98 MB</td>
</tr>
<tr>
<td></td>
<td align="right">10,000</td>
<td align="right">2.86 s</td>
<td align="right">133.88 ms</td>
<td align="right">1.88 GB</td>
<td align="right">9.41 MB</td>
</tr>
<tr>
<td><code>mutate()</code></td>
<td align="right">10</td>
<td align="right">50.8 ms</td>
<td align="right">2.85 ms</td>
<td align="right">64.62 MB</td>
<td align="right">8.12 MB</td>
</tr>
<tr>
<td></td>
<td align="right">1,000</td>
<td align="right">1.3 s</td>
<td align="right">13.83 ms</td>
<td align="right">1.95 GB</td>
<td align="right">8.41 MB</td>
</tr>
<tr>
<td></td>
<td align="right">10,000</td>
<td align="right">11.8 s</td>
<td align="right">115.23 ms</td>
<td align="right">18.72 GB</td>
<td align="right">11.63 MB</td>
</tr>
<tr>
<td><code>filter()</code></td>
<td align="right">10</td>
<td align="right">46.54 ms</td>
<td align="right">5.91 ms</td>
<td align="right">57.57 MB</td>
<td align="right">13.86 MB</td>
</tr>
<tr>
<td></td>
<td align="right">1,000</td>
<td align="right">1.45 s</td>
<td align="right">19.11 ms</td>
<td align="right">1.91 GB</td>
<td align="right">14.25 MB</td>
</tr>
<tr>
<td></td>
<td align="right">10,000</td>
<td align="right">13.89 s</td>
<td align="right">196.36 ms</td>
<td align="right">18.66 GB</td>
<td align="right">17.89 MB</td>
</tr>
<tr>
<td><code>slice_head()</code></td>
<td align="right">10</td>
<td align="right">6.27 ms</td>
<td align="right">1.1 ms</td>
<td align="right">10.35 MB</td>
<td align="right">2.74 MB</td>
</tr>
<tr>
<td></td>
<td align="right">1,000</td>
<td align="right">110.49 ms</td>
<td align="right">2.54 ms</td>
<td align="right">50.24 MB</td>
<td align="right">2.84 MB</td>
</tr>
<tr>
<td></td>
<td align="right">10,000</td>
<td align="right">3.16 s</td>
<td align="right">17.1 ms</td>
<td align="right">3.75 GB</td>
<td align="right">4.49 MB</td>
</tr>
<tr>
<td><code>distinct()</code></td>
<td align="right">10</td>
<td align="right">44.22 ms</td>
<td align="right">7.39 ms</td>
<td align="right">22.86 MB</td>
<td align="right">17.79 MB</td>
</tr>
<tr>
<td></td>
<td align="right">1,000</td>
<td align="right">465.29 ms</td>
<td align="right">18.68 ms</td>
<td align="right">120.36 MB</td>
<td align="right">19.6 MB</td>
</tr>
<tr>
<td></td>
<td align="right">10,000</td>
<td align="right">8.2 s</td>
<td align="right">135.01 ms</td>
<td align="right">8.08 GB</td>
<td align="right">27.79 MB</td>
</tr>
<tr>
<td><code>fill()</code></td>
<td align="right">10</td>
<td align="right">54.85 ms</td>
<td align="right">2.45 ms</td>
<td align="right">72.58 MB</td>
<td align="right">7.76 MB</td>
</tr>
<tr>
<td></td>
<td align="right">1,000</td>
<td align="right">1.28 s</td>
<td align="right">2.63 ms</td>
<td align="right">1.95 GB</td>
<td align="right">7.68 MB</td>
</tr>
<tr>
<td></td>
<td align="right">10,000</td>
<td align="right">12.38 s</td>
<td align="right">3.25 ms</td>
<td align="right">18.7 GB</td>
<td align="right">8.16 MB</td>
</tr>
</tbody>
</table>
<p>0.3.0 is faster for every verb and every number of groups: between 4 and 22 times faster with 10
groups, and between 21 and about 3,800 times faster with 10,000 groups. <code>fill()</code>, which no longer loops over groups,
went from 12.4 seconds to 3.3 ms. Most of the old time and memory went on comparing every row with every group, as in
Tip 1: 0.2.8 did this in <code>group_by()</code>, and every grouped verb did it again to find the groups of its result. That is
why the old times grow so quickly with the number of groups, and why <code>mutate()</code> with 10,000 groups allocated 18.7 GB.
It now allocates 11.6 MB.</p>
<p>None of this needed anything outside of base R. The loop over the groups is still there where it has to be, but it is
now the only part of the code that runs once per group.</p>
<p>{poorman} 0.3.0 will be on CRAN soon, and then you will be able to install it with</p>
<pre>install.packages(&quot;poorman&quot;)</pre>
<p>Until then, you can install it from GitHub with</p>
<pre>remotes::install_github(&quot;nathaneastwood/poorman&quot;)</pre>
<p>As always, please report any bugs or differences from {dplyr} on
<a href="https://github.com/nathaneastwood/poorman/issues" rel="nofollow" target="_blank">GitHub</a>.</p>
</div>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://nathaneastwood.github.io/2026/10/25/poorman-tips-and-tricks-for-faster-grouped-operations-in-base-r/"> Random R Ramblings</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/poorman-tips-and-tricks-for-faster-grouped-operations-in-base-r/">{poorman}: Tips and Tricks for Faster Grouped Operations in base R</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404436</post-id>	</item>
		<item>
		<title>caretS3 and ahead for Tabular and Time Series Machine Learning</title>
		<link>https://www.r-bloggers.com/2026/10/carets3-and-ahead-for-tabular-and-time-series-machine-learning/</link>
		
		<dc:creator><![CDATA[T. Moudiki]]></dc:creator>
		<pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://thierrymoudiki.github.io//blog/2026/10/25/r/caretS3-ahead</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> Exploring the integration of caretS3 and ahead for tabular and time series machine learning tasks.</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/carets3-and-ahead-for-tabular-and-time-series-machine-learning/">caretS3 and ahead for Tabular and Time Series Machine Learning</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://thierrymoudiki.github.io//blog/2026/10/25/r/caretS3-ahead"> T. Moudiki's Webpage - R</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
<h3 id="packages">Packages</h3>

<pre>#install.packages(&quot;remotes&quot;)
#install.packages(&quot;glmnet&quot;)
#install.packages(&quot;randomForest&quot;)

remotes::install_github(&quot;thierrymoudiki/caretS3&quot;)
</pre>

<h3 id="carets3carets3-tabular-machine-learning"><code>caretS3::caretS3</code> Tabular Machine Learning</h3>

<pre> data(mtcars)

 # Prepare data
 X_reg &lt;- mtcars[, -1]  # All except mpg
 y_reg &lt;- mtcars$mpg     # Target variable

 # Split into train/test
 set.seed(123)
 idx_reg &lt;- sample(nrow(X_reg), 0.7 * nrow(X_reg))
 X_train &lt;- X_reg[idx_reg, ]
 y_train &lt;- y_reg[idx_reg]
 X_test &lt;- X_reg[-idx_reg, ]
 y_test &lt;- y_reg[-idx_reg]

 mod &lt;- caretS3::caretS3(X_train, y_train, method = &quot;glmnet&quot;)

Registered S3 method overwritten by 'future':
  method               from      
  all.equal.connection parallelly

          Mazda RX4       Mazda RX4 Wag      Hornet 4 Drive             Valiant 
           22.88234            22.41295            21.12871            20.55533 
         Merc 450SE          Merc 450SL Lincoln Continental       Toyota Corona 
           14.37278            15.58185            10.76004            24.86612 
         Camaro Z28    Pontiac Firebird 
           13.55088            15.78322           Mazda RX4       Mazda RX4 Wag      Hornet 4 Drive             Valiant 
           20.09445            20.05342            19.60069            19.34515 
         Merc 450SE          Merc 450SL Lincoln Continental       Toyota Corona 
           15.35807            15.32211            12.98877            24.25831 
         Camaro Z28    Pontiac Firebird 
           15.33895            16.01282 
</pre>

<h3 id="aheaddynrmf-time-series-forecasting"><code>ahead::dynrmf</code> time series forecasting</h3>

<pre>par(mfrow=c(2, 2))
plot(ahead::dynrmf(AirPassengers, h=20, fit_func = caretS3::caretS3, fit_params = list(method=&quot;glmnet&quot;, alpha=0, lambda=0.1)))
plot(ahead::dynrmf(AirPassengers, h=20, fit_func = caretS3::caretS3, fit_params = list(method=&quot;glmnet&quot;, alpha=0.5, lambda=0.1)))
plot(ahead::dynrmf(AirPassengers, h=20, fit_func = caretS3::caretS3, fit_params = list(method=&quot;glmnet&quot;, alpha=1, lambda=0.1)))
plot(ahead::dynrmf(AirPassengers, h=20, fit_func = caretS3::caretS3, fit_params = list(method=&quot;glm&quot;)))
</pre>

<p><img src="https://i2.wp.com/thierrymoudiki.github.io/images/2026-10-25/2026-10-25-caretS3-ahead_6_0.png?w=578&#038;ssl=1" alt="image-title-here" class="img-responsive" data-recalc-dims="1" /></p>


<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://thierrymoudiki.github.io//blog/2026/10/25/r/caretS3-ahead"> T. Moudiki's Webpage - R</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/carets3-and-ahead-for-tabular-and-time-series-machine-learning/">caretS3 and ahead for Tabular and Time Series Machine Learning</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404434</post-id>	</item>
		<item>
		<title>Price elasticity of demand for crude oil by @ellis2013nz</title>
		<link>https://www.r-bloggers.com/2026/10/price-elasticity-of-demand-for-crude-oil-by-ellis2013nz/</link>
		
		<dc:creator><![CDATA[free range statistics - R]]></dc:creator>
		<pubDate>Sat, 24 Oct 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://freerangestats.info/blog/2026/10/25/price-elasticity-oil</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> This blog post is about me trying to make a better version of an existing diagram that I think is important, but difficult to read in the version it keeps popping up on my Bluesky feed.</p>
<p>Robin J Brooks of the Brookings Institution has been using a usef...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/price-elasticity-of-demand-for-crude-oil-by-ellis2013nz/">Price elasticity of demand for crude oil by @ellis2013nz</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://freerangestats.info/blog/2026/10/25/price-elasticity-oil"> free range statistics - R</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
<p>This blog post is about me trying to make a better version of an existing diagram that I think is important, but difficult to read in the version it keeps popping up on my Bluesky feed.</p>

<p>Robin J Brooks of the <a href="https://www.brookings.edu/people/robin-brooks/" rel="nofollow" target="_blank">Brookings Institution</a> has been using a useful diagram, I think since March 2026, to present a set of illustrative theoretical impacts of the USA-Iran war on crude oil prices. For example, he reproduces it in <a href="https://robinjbrooks.substack.com/p/whats-priced-into-brent-at-117" rel="nofollow" target="_blank">this recent blog post</a>. Relative to many other voices, Brooks has generally been bearish on crude oil prices in this conflict, with his approach very much based on this diagram. The diagram is attributed to <a href="https://www.haver.com/" rel="nofollow" target="_blank">Haver Analytics</a>, although I can’t find the original.</p>

<p>I think it’s an important graphic because it does make us confront that the demand curve for crude oil isn’t vertical. There are in fact estimates of the price elasticity of demand for crude oil, and they vary, but certainly sometimes they come out somewhere between -0.1 and -0.2. Usually the price elasticity of demand is introduced to beginner economists as a value of -0.1 means “a price increase of 1% will lead to a decrease in the quantity demanded of 0.1%”. But for this exercise, we will turn the relationship around—if the equilibrium point where supply meets demand is forced down by 0.1% (because of a shift in the supply curve), this implies a price increase of approximately 1%.</p>

<p>In the case of the current war, at its peak, events removed an amount of oil from the market of somewhere around 10% of normal global oil supply. Up to 20% of world supply has been exposed to risk, although I don’t think we ever reached a reduction of that magnitude. The amount removed is much less right now at the time of me writing (meaning, oil is flowing at a much higher rate than a few months ago), but who knows where it will be in a few weeks. The above reasoning implies an impact of a 10% cut in supply on the price of 100% (ie doubling) if the price elasticity of demand is -0.1; or around 50% if it is -0.2. This is basically what the diagram used by Brooks shows.</p>

<p>In doing this, the graphic uses a linear elasticity approximation. I’m not sure this really applies for the scale of change we’re talking about here, with a 10% or 20% drop in supply. But it’s a starting point.</p>

<p>Also note that none of this factors in a change in the demand curve. These are changes up and down the existing demand curve. Over a longer period, the curve will shift with changes in income, policy, buyer attitudes and technology, in particular through the obvious mechanism of accelerated transition to clean, renewable electricity. For now, we are putting those changes aside.</p>

<p>We’re also putting aside how drawing down on existing inventory can (and certainly did) cushion markets from the reduction in supply. We’re trying to illustrate a point and get a plausible range given various assumptions; not actually forecast prices.</p>

<h2 id="the-perspective-plot">The perspective plot</h2>

<p>Unfortunately, I find the chart from Haver Analytics very difficult to follow. It’s trying to show the relationship between two key variables—the oil successfully exported from the Gulf, and resulting impact on prices—and one uncertain parameter, the short-term price elasticity of demand for crude oil. We don’t know this elasticity for sure, but it’s plausibly somewhere between -0.1 and -0.2. So we want to see a range of impacts on price given this unknown parameter.</p>

<p>Note: in the plots below, I follow Haver Analytics in showing the absolute magnitude of the price elasticity of demand, dropping the negative sign.</p>

<p>To deal with this, the original uses a 3-dimensional perspective plot. Here’s my rough reproduction in R of the original chart:</p>

<object type="image/svg+xml" data="https://freerangestats.info/img/0335-persp.svg" width="450"><img src="https://i0.wp.com/freerangestats.info/img/0335-persp.png?w=450&#038;ssl=1" data-recalc-dims="1" /></object>

<p>I think my version is actually a bit easier to read than Haver Analytics’ original because of the colour shading, but it’s only a marginal improvement. The issue is the common one with these sorts of 3-dimensional plots—it’s just very hard to read across from the flexing surface to the values on the three axes, and it’s even harder to take in a general trend or tendency in the way we find straightforward with a 2-dimensional scatter plot or line plot.</p>

<p>I simulated the illustrative data behind the diagram and made that 3-dimensional perspective plot with this code:</p>

<figure class="highlight"><pre>library(tidyverse)
library(plot3D)
library(scales)
library(glue)
library(RColorBrewer)

#---------------Simulating data---------------------------

global_oil_consumption  &lt;- 100
original_gulf_exports &lt;- 20
prewar_oil_price &lt;- 72.5

elasticities &lt;- seq(from = 0.1, to = 0.2, length.out = 11)
exports &lt;- 2:original_gulf_exports
length(exports)
length(elasticities)

d &lt;- expand_grid(
    elasticity = elasticities,
    export = exports
) |&gt;
    mutate(
        lost_oil = (original_gulf_exports - export) / global_oil_consumption ,
        price_rise = lost_oil / elasticity * 100
    )

#---------------common caption and title across all plots-------------------

the_caption &lt;- str_wrap(
    glue(
        &quot;Illustrative calculation assuming global oil consumption of &quot;,
        &quot;{global_oil_consumption } million barrels per day and pre-war Gulf exports of &quot;,
        &quot;{original_gulf_exports} million barrels per day. &quot;,
        &quot;Re-worked by freerangestats.info based on original analysis by Haver Analytics.&quot;
    ),
    115
)
the_title &lt;- &quot;Illustrative impact of Gulf export cuts on oil prices&quot;
#----------------Perspective plot---------------
# this recreates the original plot by Haver Analytics

prices_matrix &lt;- d |&gt;
    arrange(export, elasticity) |&gt;
    select(elasticity, export, price_rise) |&gt;
    pivot_wider(names_from = elasticity, values_from = price_rise) |&gt;
    select(-export) |&gt;
    as.matrix()

reds &lt;- colorRampPalette(c(&quot;mistyrose&quot;, &quot;darkred&quot;))(100)

persp3D(
    x = exports,
    y = elasticities,
    z = prices_matrix,
    col = reds,
    xlab = &quot;\nOil exported from the Gulf&quot;,
    ylab = &quot;\n\nPrice elasticity of demand&quot;,
    zlab = &quot;\nRise in oil prices (%)&quot;,
    main = the_title,
    # box type must be one of “b”, “b2”, “f”, “g”, “bl”, “bl2”, “u”, “n”
    bty = &quot;g&quot;,
    ticktype = &quot;detailed&quot;,
    colkey = FALSE
)

mtext(
    the_caption,
    side = 1,
    line = 4,
    cex = 0.8,
    col = &quot;grey30&quot;
)</pre></figure>

<h2 id="improvement-1-heatmap">Improvement 1: heatmap</h2>

<p>Often, a perspective plot is improved by turning it into some kind of heatmap. These use the same type of data—a grid of values on x and y axes, with a variable of interest on a third dimension. In the perspective plot, that third variable is mapped to the z vertical axis; in a heatmap we map it to the colour of the fill.</p>

<p>Here’s my first attempt at improving the perspective plot by redrawing it as a heatmap:</p>

<object type="image/svg+xml" data="https://freerangestats.info/img/0335-heatmap.svg" width="450"><img src="https://i0.wp.com/freerangestats.info/img/0335-heatmap.png?w=450&#038;ssl=1" data-recalc-dims="1" /></object>

<p>I think this is already a big improvement, but it’s come at the cost of nearly turning the graphic into a table. This is easier to read, but doesn’t feel like we’re really using the power of visualisation.</p>

<p>Here’s the code that draws the heatmap version:</p>

<figure class="highlight"><pre>#-----------------Heat map--------------------
elasticities2 &lt;- c(0.1, 0.12, 0.14, 0.16, 0.18, 0.2)
exports2 &lt;- 1:10 * 2

p1 &lt;- d |&gt;
    filter(round(elasticity, 2) %in% elasticities2) |&gt;
    filter(export %in% exports2) |&gt;
    mutate(text_col = ifelse(price_rise &gt;= 100, &quot;Pale&quot;, &quot;Dark&quot;)) |&gt;
    ggplot(aes(x = export, y = elasticity, fill = price_rise)) +
    geom_tile() +
    geom_text(
        aes(label = percent(price_rise, scale = 1), colour = text_col),
        size = 4
    ) +
    scale_fill_gradientn(colours = reds, label = percent_format(scale = 1)) +
    scale_y_continuous(breaks = elasticities2) +
    scale_x_continuous(breaks = exports2) +
    scale_colour_manual(
        values = c(&quot;Pale&quot; = &quot;white&quot;, &quot;Dark&quot; = &quot;black&quot;),
        guide = &quot;none&quot;
    ) +
    labs(
        title = the_title,
        subtitle = str_wrap(
            &quot;Illustrative price increase for crude oil depends on the price elasticity of demand and the amount of oil that is exported from the Gulf.&quot;,
            100
        ),
        x = &quot;Oil exported from the Gulf (in million barrels per day)&quot;,
        y = &quot;Price elasticity of demand for crude oil&quot;,
        fill = &quot;Price increase for crude oil&quot;,
        caption = the_caption
    )

print(p1)</pre></figure>

<h2 id="improvement-2-line-plot">Improvement 2: line plot</h2>

<p>So I had a go at a line plot. I think the most important relationship here is between the amount of oil successfully exported from the Gulf and the change in world crude oil price. So I make this the x and y axes. The visual relationship between x and y is the most striking thing for many readers of charts; certainly for me. The uncertainty about the price elasticity of demand is of secondary interest, and we can show that just by drawing a different line for a bunch of different plausible values.</p>

<p>Here’s what I came up with:</p>

<object type="image/svg+xml" data="https://freerangestats.info/img/0335-lines.svg" width="450"><img src="https://i1.wp.com/freerangestats.info/img/0335-lines.png?w=450&#038;ssl=1" data-recalc-dims="1" /></object>

<p>I think this is definitely the best version.</p>

<p>One of its advantages is that I can use a secondary axis to convert the percentage price increases into an implied dollar value per barrel, based on a pre-war price of around US$72.50 per barrel. This is a nice addition for comprehensibility.</p>

<p>I think the fan of colour-coded lines is also a good way of showing the sensitivity of results to the different possible assumptions about the price elasticity of demand. I’ve deliberately colour-coded these based on the distance from the middle of the range of possible assumptions, rather than from lowest to highest - because I think this better draws attention to this as a type of uncertainty.</p>

<p>But the most important thing with this chart is definitely that we have put the two most important variables on the x and y axes. It means, for example, we can simply see the implied relationship as a trend, which is so hard to see in the original.</p>

<p>It also makes it easy to read across from today’s price and up from the crude exports level (no-one knows what this is today, but say somewhere between 7 and 15 million barrels a day) to hit the fan of different lines, and see a) what any particular value implies about the price elasticity of demand and/or b) if there is an extra price impact being built in, perhaps from other information on inventories, perceived risk, known coming changes in supply and demand, etc.</p>

<p>For what it’s worth, with plausible values for today, the chart implies that there is a bit extra built into the prices. My presumption is that this is likely a (whether rationally founded or not) fear that the exports could well rapidly drop from the current reasonably high levels.</p>

<p>Here’s the code for drawing the line plot version:</p>

<figure class="highlight"><pre>#-----------------line plot------------------
annot_col &lt;- &quot;grey12&quot;

p2 &lt;- d |&gt;
    mutate(
        elasticity_distance = abs(elasticity - 0.15),
        is_midpoint = ifelse(round(elasticity, 2) == 0.15, &quot;solid&quot;, &quot;dashed&quot;)
    ) |&gt;
    ggplot(aes(
        x = export,
        y = price_rise,
        group = elasticity,
        colour = elasticity_distance
    )) +
    geom_line(aes(linetype = is_midpoint)) +
    geom_text(
        data = filter(
            d,
            export == min(export),
            round(elasticity, 2) %in% c(0.1, 0.15, 0.2)
        ),
        aes(label = glue(&quot;Elasticity = {elasticity}&quot;)),
        hjust = 1,
        position = position_nudge(x = -0.1),
        colour = annot_col,
        size = 2.8
    ) +
    scale_linetype_manual(
        values = c(&quot;solid&quot; = 1, &quot;dashed&quot; = 5),
        guide = &quot;none&quot;
    ) +
    scale_y_continuous(
        label = percent_format(scale = 1),
        sec.axis = sec_axis(
            ~ prewar_oil_price * (1 + . / 100),
            name = &quot;Implied crude oil price (US$/barrel)&quot;,
            label = dollar
        )
    ) +
    annotate(
        &quot;text&quot;,
        x = 17,
        y = 0,
        hjust = 1,
        size = 2.5,
        label = &quot;Pre-war situation&quot;,
        colour = annot_col
    ) +
    annotate(
        &quot;segment&quot;,
        x = 17,
        xend = 19.5,
        y = 0,
        yend = 0,
        arrow = arrow(length = unit(0.15, &quot;cm&quot;)),
        colour = annot_col
    ) +
    expand_limits(x = c(0, 20)) +
    scale_colour_gradientn(
        colours = brewer.pal(9, &quot;Blues&quot;)[9:4],
        breaks = c(0, 0.025, 0.05),
        limits = c(0, 0.05)
    ) +
    labs(
        x = &quot;Oil exported from the Gulf (million barrels per day)&quot;,
        y = &quot;Price increase for crude oil&quot;,
        colour = &quot;Distance from midpoint of assumed elasticity range:&quot;,
        title = the_title,
        subtitle = str_wrap(
            &quot;Illustrative price increase for crude oil depends on the price elasticity of demand and the amount of oil that is exported from the Gulf.&quot;,
            100
        ),
        caption = the_caption
    ) +
    theme(
        panel.background = element_rect(fill = &quot;white&quot;),
        panel.grid.major = element_line(colour = &quot;grey88&quot;),
        panel.grid.minor = element_blank()
    )

print(p2)</pre></figure>

<p>That’s all for today. I like my final graphic, but I like the underlying analytical framework even more. I haven’t set out to criticise the original graphic, but to redraw the chart in a way that’s easier for me to read. Turns out the most important thing is to put the two most important variables on the x and y axes.</p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://freerangestats.info/blog/2026/10/25/price-elasticity-oil"> free range statistics - R</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/price-elasticity-of-demand-for-crude-oil-by-ellis2013nz/">Price elasticity of demand for crude oil by @ellis2013nz</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404432</post-id>	</item>
		<item>
		<title>Let’s peel the world</title>
		<link>https://www.r-bloggers.com/2026/10/lets-peel-the-world/</link>
		
		<dc:creator><![CDATA[dieghernan]]></dc:creator>
		<pubDate>Fri, 23 Oct 2026 22:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://dieghernan.github.io/202610_orange-peel/</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> What would happen if we peeled the Earth like an orange? The<br />
interrupted Goode homolosine projection<br />
gives us something close to that! This equal-area projection preserves relative<br />
areas while splitting the world map into lobes. Add a few shades of orange and<br />
our world is ready to peel.</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/lets-peel-the-world/">Let’s peel the world</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://dieghernan.github.io/202610_orange-peel/"> One World | R packages, maps and open-source projects - R Project</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

		
		<h3>Do you know the interrupted Goode homolosine projection?</h3>
		
  		
			<img src='https://dieghernan.github.io/assets/img/blog/202610_goode-map-1.webp' >
			
	<p>2 min.</p>
			<p>What would happen if we peeled the Earth like an orange? The
<a href="https://proj.org/en/stable/operations/projections/igh.html" rel="nofollow" target="_blank">interrupted Goode homolosine projection</a>
gives us something close to that! This equal-area projection preserves relative
areas while splitting the world map into lobes. Add a few shades of orange and
our world is ready to peel.</p>

<p>Let’s make it with <strong>sf</strong>, <strong>giscoR</strong> and <strong>ggplot2</strong>. First, we get the
coastlines and draw the outline, following the cuts between the lobes.</p>

<pre>library(sf)
library(ggplot2)
library(giscoR)

world &lt;- gisco_get_coastal_lines(resolution = 10)
goode_crs &lt;- &quot;+proj=igh&quot;
lat &lt;- c(90:-90, rep(c(-90:0, 0:-90), 3), -90:90, 90:0, 0:90, 90)
lon &lt;- c(
  rep(180, 181),
  rep(c(80.01, 79.99, -19.99, -20.01, -99.99, -100.01), each = 91),
  rep(-180, 181),
  rep(c(-40.01, -39.99), each = 91),
  180
)
outline &lt;- st_sfc(st_polygon(list(cbind(lon, lat))), crs = 4326) |&gt;
  st_transform(goode_crs)
rectangle &lt;- st_as_sfc(st_bbox(outline) * 1.1)
mask &lt;- st_difference(rectangle, outline)
</pre>

<p>Next, we draw the meridians and parallels within each lobe so that they do not
connect across the cuts.</p>

<pre>lobes &lt;- list(
  c(-180, -40, 0, 90), c(-40, 180, 0, 90),
  c(-180, -100, -90, 0), c(-100, -20, -90, 0),
  c(-20, 80, -90, 0), c(80, 180, -90, 0)
)
lines &lt;- lapply(lobes, function(b) {
  west &lt;- b[1] + 0.01
  east &lt;- b[2] - 0.01
  south &lt;- max(b[3], -89.99)
  north &lt;- min(b[4], 89.99)
  meridians &lt;- seq(-180, 180, 30)
  meridians &lt;- meridians[meridians &gt; west & meridians &lt; east]
  parallels &lt;- seq(-60, 60, 30)
  parallels &lt;- parallels[parallels &gt;= south & parallels &lt;= north]
  c(
    lapply(meridians, function(x) {
      st_linestring(cbind(x, seq(south, north, length.out = 181)))
    }),
    lapply(parallels, function(y) {
      st_linestring(cbind(seq(west, east, length.out = 361), y))
    })
  )
})
grat &lt;- st_sfc(do.call(c, lines), crs = 4326) |&gt;
  st_transform(goode_crs)
</pre>

<p>Finally, we add the orange palette. The mask hides anything outside the outline
and keeps the edges of our peel tidy.</p>

<pre>ggplot() +
  geom_sf(data = outline, fill = &quot;#FFF0D9&quot;, colour = NA) +
  geom_sf(data = world, fill = &quot;#F9B566&quot;, colour = &quot;#B34D08&quot;, linewidth = 0.25) +
  geom_sf(data = grat, fill = NA, colour = &quot;#E8A45B&quot;, linewidth = 0.25) +
  geom_sf(data = mask, fill = &quot;white&quot;, colour = NA) +
  geom_sf(data = outline, fill = NA, colour = &quot;#E87516&quot;, linewidth = 0.4) +
  coord_sf(
    crs = goode_crs,
    xlim = st_bbox(outline)[c(&quot;xmin&quot;, &quot;xmax&quot;)],
    ylim = st_bbox(outline)[c(&quot;ymin&quot;, &quot;ymax&quot;)],
    expand = FALSE,
    datum = NA
  ) +
  theme_void() +
  theme(
    plot.background = element_rect(fill = &quot;white&quot;, colour = NA),
    plot.title = element_text(color = &quot;#E87516&quot;, face = &quot;bold&quot;)
  ) +
  labs(
    title = &quot;Let's peel the world&quot;,
    caption = &quot;Interrupted Goode homolosine projection&quot;
  )
</pre>

<p><img src="https://dieghernan.github.io/assets/img/blog/202610_goode-map-1.webp" alt="World map with orange coastlines and graticules in the interrupted Goode homolosine projection, resembling a flattened orange peel." width="450" /></p>

<h2 id="references">References</h2>

<p>The outline and masking approach follows Claus O. Wilke’s
<a href="https://wilkelab.org/practicalgg/articles/goode.html" rel="nofollow" target="_blank">Interrupted Goode homolosine</a>
example. For more on map projections, see Pyranhia’s
<a href="https://pyranhia.eu/posts/202609-carto-proj-avancees/carto-proj-avancees.html" rel="nofollow" target="_blank">Projections cartographiques : le guide pour ne pas y laisser sa santé mentale</a>
(in French).</p>

		
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://dieghernan.github.io/202610_orange-peel/"> One World | R packages, maps and open-source projects - R Project</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/lets-peel-the-world/">Let’s peel the world</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404419</post-id>	</item>
		<item>
		<title>[Statistics] Understand Simpson&#8217;s paradox by simulation</title>
		<link>https://www.r-bloggers.com/2026/10/statistics-understand-simpsons-paradox-by-simulation/</link>
		
		<dc:creator><![CDATA[R on Zhenguo Zhang&#039;s Blog]]></dc:creator>
		<pubDate>Fri, 23 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://fortune9.netlify.app/2026/10/23/python-simpsons-paradox/</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> Zhenguo Zhang's Blog https://fortune9.netlify.app/2026/10/23/python-simpsons-paradox/ -</p>
<p>Introduction<br />
Simpson’s paradox occurs when the direction of an association reverses after data from several groups are combined. For example, A can have a higher success rate than B in every subgroup, yet a lower success rate overall.<br />
In this ...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/statistics-understand-simpsons-paradox-by-simulation/">[Statistics] Understand Simpson’s paradox by simulation</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://fortune9.netlify.app/2026/10/23/python-simpsons-paradox/"> R on Zhenguo Zhang&#039;s Blog</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
Zhenguo Zhang&#8217;s Blog https://fortune9.netlify.app/2026/10/23/python-simpsons-paradox/ &#8211;


<div id="introduction" class="section level2">
<h2>Introduction</h2>
<p><a href="https://en.wikipedia.org/wiki/Simpson%27s_paradox" rel="nofollow" target="_blank">Simpson’s paradox</a> occurs when the direction of an association reverses after data from several groups are combined. For example, A can have a higher success rate than B in every subgroup, yet a lower success rate overall.</p>
<p>In this post, we will simulate exam outcomes in Python, examine the reversal using logistic regression and a plotnine figure, and explain why it happens. All Python code is embedded in this R Markdown document and runs through <a href="https://rstudio.github.io/reticulate/articles/r_markdown.html" rel="nofollow" target="_blank">reticulate</a>.</p>
</div>
<div id="setup" class="section level2">
<h2>Setup</h2>
<p>You can skip this section unless you want to run the code yourself.</p>
<p>We use NumPy to simulate outcomes, pandas to summarize the data, statsmodels to fit logistic regression models, and <a href="https://plotnine.org/" rel="nofollow" target="_blank">plotnine</a> to create the figure. The R package <code>reticulate</code> lets us run Python code within this document. If needed, install it with <code>install.packages(&quot;reticulate&quot;)</code>.</p>
<p>I used the python points to my local executable at <code>/mnt/windows/Program/ubuntu/pyenv/versions/3.13.9/bin/python</code>. To find the <code>python3</code> executable on your PATH, run:</p>
<pre>Sys.which(&quot;python3&quot;)</pre>
<p>The setup chunk uses <code>reticulate::use_python()</code> to select a specific Python executable. Replace the path with the executable for your own Python installation or virtual environment before running any Python chunks. If the <code>RETICULATE_PYTHON</code> environment variable is set, it takes precedence over <code>use_python()</code>.</p>
<p>Install the Python packages in the same environment. In the command below, replace <code>python</code> with your chosen executable if necessary:</p>
<pre>python -m pip install numpy pandas plotnine</pre>
<p>You will also need statsmodels for the regression models below. Reticulate runs all Python chunks in a shared session, so objects created in one chunk remain available in subsequent chunks.</p>
</div>
<div id="a-simulated-example-with-a-confounding-factor" class="section level2">
<h2>A simulated example with a confounding factor</h2>
<p>We will examine the relationship between study program and exam pass rate, with exam difficulty as a confounding factor.</p>
<p>Students belong to either program A or program B and choose between an easy exam and a hard exam. We deliberately make their choices uneven: 90% of students in program A choose the hard exam, while 90% of students in program B choose the easy exam.</p>
<p>The table below gives the number of students and the probability of passing for each combination of program and exam difficulty.</p>
<table>
<thead>
<tr class="header">
<th>Program</th>
<th>Exam difficulty</th>
<th align="right">Students</th>
<th align="right">Pass probability</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>A</td>
<td>Easy</td>
<td align="right">1,000</td>
<td align="right">90%</td>
</tr>
<tr class="even">
<td>A</td>
<td>Hard</td>
<td align="right">9,000</td>
<td align="right">40%</td>
</tr>
<tr class="odd">
<td>B</td>
<td>Easy</td>
<td align="right">9,000</td>
<td align="right">80%</td>
</tr>
<tr class="even">
<td>B</td>
<td>Hard</td>
<td align="right">1,000</td>
<td align="right">30%</td>
</tr>
</tbody>
</table>
<p>Using these group sizes and probabilities, let’s simulate a dataset of 20,000 students. Each row represents one student, and the outcome is coded as 1 for a pass or 0 for a failure.</p>
<p>We keep the group sizes fixed and generate each student’s outcome independently with <a href="https://numpy.org/doc/stable/reference/random/generated/numpy.random.Generator.binomial.html" rel="nofollow" target="_blank"><code>rng.binomial(1, p)</code></a>, where <code>p</code> is the pass probability for that student’s group. A fixed random seed makes the example reproducible.</p>
<pre>import numpy as np
import pandas as pd
# Fixed group sizes; independently simulate each student&#39;s pass/fail outcome.
rng = np.random.default_rng(2026)
design = [
    (&quot;Easy&quot;, &quot;A&quot;, 1000, 0.90),
    (&quot;Easy&quot;, &quot;B&quot;, 9000, 0.80),
    (&quot;Hard&quot;, &quot;A&quot;, 9000, 0.40),
    (&quot;Hard&quot;, &quot;B&quot;, 1000, 0.30),
]
df = pd.concat(
    [
        pd.DataFrame({
            &quot;difficulty&quot;: difficulty,
            &quot;program&quot;: program,
            &quot;passed&quot;: rng.binomial(1, probability, size=n),
        })
        for difficulty, program, n, probability in design
    ],
    ignore_index=True,
)</pre>
<p>Here are the first few rows of the simulated dataset:</p>
<pre>print(df.head())
  difficulty program  passed
0       Easy       A       1
1       Easy       A       1
2       Easy       A       1
3       Easy       A       1
4       Easy       A       1</pre>
<p>We can check the number of students in each group:</p>
<pre>df[[&quot;program&quot;,&quot;difficulty&quot;]].value_counts().reset_index(name=&quot;Count&quot;)
  program difficulty  Count
0       A       Hard   9000
1       B       Easy   9000
2       A       Easy   1000
3       B       Hard   1000</pre>
</div>
<div id="examine-the-pass-rates-in-the-simulated-data" class="section level2">
<h2>Examine the pass rates in the simulated data</h2>
<p>First, let’s fit a logistic regression model with <code>program</code> as the only predictor of whether a student passes.</p>
<pre>import statsmodels.formula.api as smf
logit_fit = smf.logit(&quot;passed ~ C(program)&quot;, data=df).fit(disp=False)
print(logit_fit.summary())
                           Logit Regression Results                           
==============================================================================
Dep. Variable:                 passed   No. Observations:                20000
Model:                          Logit   Df Residuals:                    19998
Method:                           MLE   Df Model:                            1
Date:                Sun, 25 Oct 2026   Pseudo R-squ.:                 0.07323
Time:                        22:15:47   Log-Likelihood:                -12511.
converged:                       True   LL-Null:                       -13499.
Covariance Type:            nonrobust   LLR p-value:                     0.000
===================================================================================
                      coef    std err          z      P&gt;|z|      [0.025      0.975]
-----------------------------------------------------------------------------------
Intercept          -0.2322      0.020    -11.534      0.000      -0.272      -0.193
C(program)[T.B]     1.3197      0.031     43.140      0.000       1.260       1.380
===================================================================================</pre>
<p>The coefficient for program B, relative to program A, is 1.3197 on the log-odds scale. This positive coefficient indicates that students in program B have a higher probability of passing. The small p-value shows that the association is statistically significant.</p>
<p>Now let’s add exam difficulty to the model:</p>
<pre>logit_fit_with_confounder = smf.logit(&quot;passed ~ C(program) + C(difficulty)&quot;, data=df).fit(disp=False)
print(logit_fit_with_confounder.summary())
                           Logit Regression Results                           
==============================================================================
Dep. Variable:                 passed   No. Observations:                20000
Model:                          Logit   Df Residuals:                    19997
Method:                           MLE   Df Model:                            2
Date:                Sun, 25 Oct 2026   Pseudo R-squ.:                  0.1441
Time:                        22:15:47   Log-Likelihood:                -11554.
converged:                       True   LL-Null:                       -13499.
Covariance Type:            nonrobust   LLR p-value:                     0.000
=========================================================================================
                            coef    std err          z      P&gt;|z|      [0.025      0.975]
-----------------------------------------------------------------------------------------
Intercept                 1.8555      0.059     31.210      0.000       1.739       1.972
C(program)[T.B]          -0.4760      0.058     -8.185      0.000      -0.590      -0.362
C(difficulty)[T.Hard]    -2.2809      0.059    -38.892      0.000      -2.396      -2.166
=========================================================================================</pre>
<p>After we account for exam difficulty, the coefficient for program B becomes negative. The model now indicates that students in program B have a lower probability of passing than students in program A when exam difficulty is held constant.</p>
<p>To see the reversal directly, let’s compare the pass rates within each difficulty group with the overall rates for each program:</p>
<pre>by_group = (
    df.groupby([&quot;difficulty&quot;, &quot;program&quot;])[&quot;passed&quot;]
    .agg(n=&quot;size&quot;, passes=&quot;sum&quot;, rate=&quot;mean&quot;)
    .reset_index()
)
pooled = (
    df.groupby(&quot;program&quot;)[&quot;passed&quot;]
    .agg(n=&quot;size&quot;, passes=&quot;sum&quot;, rate=&quot;mean&quot;)
    .reset_index()
)
print(&quot;Within difficulty groups:&quot;)
print(by_group.to_string(index=False, float_format=&quot;%.4f&quot;))
print(&quot;\nAll students combined:&quot;)
print(pooled.to_string(index=False, float_format=&quot;%.4f&quot;))
Within difficulty groups:
difficulty program    n  passes   rate
      Easy       A 1000     886 0.8860
      Easy       B 9000    7169 0.7966
      Hard       A 9000    3536 0.3929
      Hard       B 1000     310 0.3100

All students combined:
program     n  passes   rate
      A 10000    4422 0.4422
      B 10000    7479 0.7479</pre>
<p>Plotting these rates side by side makes the reversal easy to see. The first two panels compare programs within each difficulty group; the third combines all students:</p>
<pre>from plotnine import (
    aes, facet_wrap, geom_col, geom_text, ggplot, labs,
    scale_fill_manual, scale_y_continuous, theme, theme_minimal,
)

plot_data = pd.concat(
    [by_group, pooled.assign(difficulty=&quot;Combined&quot;)], ignore_index=True
)
plot_data[&quot;difficulty&quot;] = pd.Categorical(
    plot_data[&quot;difficulty&quot;], categories=[&quot;Easy&quot;, &quot;Hard&quot;, &quot;Combined&quot;], ordered=True
)
plot_data[&quot;label&quot;] = plot_data[&quot;rate&quot;].map(lambda rate: f&quot;{rate:.1%}&quot;)

plot = (
    ggplot(plot_data, aes(x=&quot;program&quot;, y=&quot;rate&quot;, fill=&quot;program&quot;))
    + geom_col(width=0.65)
    + geom_text(aes(label=&quot;label&quot;), va=&quot;bottom&quot;, nudge_y=0.02, size=10)
    + facet_wrap(&quot;difficulty&quot;, nrow=1)
    + scale_fill_manual(values={&quot;A&quot;: &quot;#0072B2&quot;, &quot;B&quot;: &quot;#D55E00&quot;})
    + scale_y_continuous(
        limits=(0, 1), breaks=[0, 0.25, 0.5, 0.75, 1],
        labels=lambda values: [f&quot;{value:.0%}&quot; for value in values],
    )
    + labs(
        title=&quot;Simpson&#39;s paradox: the comparison reverses when groups are pooled&quot;,
        subtitle=&quot;Program A has a higher pass rate in each difficulty group&quot;,
        x=&quot;Program&quot;, y=&quot;Pass rate&quot;,
        caption=&quot;Simulated data: A has 90% hard cases; B has 90% easy cases.&quot;,
    )
    + theme_minimal(base_size=12)
    + theme(legend_position=&quot;none&quot;, figure_size=(10, 4))
)
plot.show()</pre>
<div class="figure"><span style="display:block;" id="fig:plot-reversal"></span>
<img src="https://i2.wp.com/fortune9.netlify.app/2026/10/23/python-simpsons-paradox/index_files/figure-html/plot-reversal-1.png?w=450&#038;ssl=1" alt="Pass rates within difficulty groups and after pooling students." data-recalc-dims="1" />
<p class="caption">
Figure 1: Pass rates within difficulty groups and after pooling students.
</p>
</div>
</div>
<div id="why-combining-the-groups-changes-the-result" class="section level2">
<h2>Why combining the groups changes the result</h2>
<p>Each program’s overall pass rate is a <a href="https://en.wikipedia.org/wiki/Weighted_arithmetic_mean" rel="nofollow" target="_blank">weighted average</a> of its pass rates on easy and hard exams. The weights are the proportions of students taking each exam within that program.</p>
<p>For program A, 10% of students take the easy exam and 90% take the hard exam. Its overall pass rate is therefore:</p>
<pre>A: (1,000 / 10,000) × (886 / 1,000)
 + (9,000 / 10,000) × (3,536 / 9,000)
 = 0.4422 = 44.22%</pre>
<p>For program B, the proportions are reversed: 90% take the easy exam and 10% take the hard exam. Its overall pass rate is:</p>
<pre>B: (9,000 / 10,000) × (7,169 / 9,000)
 + (1,000 / 10,000) × (310 / 1,000)
 = 0.7479 = 74.79%</pre>
<p>A has a higher pass rate at each difficulty level, but most of its students take the hard exam. B has a lower pass rate at each difficulty level, but most of its students take the easy exam. Because the subgroup rates receive <strong>different weights</strong>, the overall comparison favors B.</p>
<p>The reversal is already present in the probabilities we specified, before we simulate any outcomes. Using those probabilities, the expected overall pass rates are:</p>
<pre>A: 0.10 × 0.90 + 0.90 × 0.40 = 0.45
B: 0.90 × 0.80 + 0.10 × 0.30 = 0.75</pre>
<p>Random sampling introduces variation around these expected rates. The reversal itself comes from the combination of pass probabilities and the uneven distribution of exam difficulty across programs.</p>
</div>
<div id="difficulty-is-a-confounder-here" class="section level2">
<h2>Difficulty is a confounder here</h2>
<p>In this simulation, students in program A are more likely to choose hard exams than students in program B. Exam difficulty is therefore associated with program membership, and it also affects the probability of passing. It plays the role of a <a href="https://en.wikipedia.org/wiki/Confounding" rel="nofollow" target="_blank">confounder</a>: ignoring it mixes the association between program and passing with differences in exam difficulty. Accounting for difficulty lets us compare the programs among students taking exams of the same difficulty.</p>
<p>A reversal alone does not establish a causal effect or tell us which variables to adjust for. Those decisions require assumptions about how the variables relate. For example, conditioning on a <a href="https://en.wikipedia.org/wiki/Collider_(statistics)" rel="nofollow" target="_blank">collider</a> can introduce bias. Dividing the data into subgroups based on every available variable is therefore not a general solution.</p>
</div>
<div id="takeaways" class="section level2">
<h2>Takeaways</h2>
<ul>
<li>Simpson’s paradox occurs when an association points in the same direction within every subgroup but reverses when the groups are combined.</li>
<li>Examine group sizes as well as rates: differences in group composition determine the weights in the overall comparison.</li>
<li>Use knowledge of the data-generating process to guide causal interpretation and adjustment.</li>
</ul>
</div>
&#8211; https://fortune9.netlify.app/2026/10/23/python-simpsons-paradox/ &#8211; 
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://fortune9.netlify.app/2026/10/23/python-simpsons-paradox/"> R on Zhenguo Zhang&#039;s Blog</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/statistics-understand-simpsons-paradox-by-simulation/">[Statistics] Understand Simpson’s paradox by simulation</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404472</post-id>	</item>
		<item>
		<title>Automatically center a projection on the plotted map</title>
		<link>https://www.r-bloggers.com/2026/10/automatically-center-a-projection-on-the-plotted-map/</link>
		
		<dc:creator><![CDATA[A.M. Barbosa]]></dc:creator>
		<pubDate>Thu, 22 Oct 2026 18:07:29 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">http://modtools.wordpress.com/?p=3584</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> Following this previous post and this discussion on Mastodon, I wrote a function that wraps a combination of functions from the ‘terra’ package to project a SpatRaster or SpatVector layer while automatically centering the projection on that layer, to minimize … Continue reading →</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/automatically-center-a-projection-on-the-plotted-map/">Automatically center a projection on the plotted map</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://modtools.wordpress.com/2026/10/22/automatically-center-a-projection-on-the-plotted-map/"> modTools</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p class="wp-block-paragraph">Following <a href="https://modtools.wordpress.com/2026/08/04/centering-a-map-projection-on-the-mapped-region/" rel="nofollow" target="_blank">this previous post</a> and <a href="https://fosstodon.org/@ambarbosa/117037425369904587" rel="nofollow" target="_blank">this discussion on Mastodon</a>, I wrote a function that wraps a combination of functions from the <a href="https://cran.r-project.org/web/packages/terra/index.html" rel="nofollow" target="_blank">‘terra’ package</a> to project a SpatRaster or SpatVector layer while automatically centering the projection on that layer, to minimize visual distortion:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
projectCentered &lt;- function(x,  # SpatRaster or SpatVector                            proj = &quot;laea&quot;  # +proj parameter for PROJ.4 string                            ) {  # version 1.0, by A. Marcia Barbosa  stopifnot(inherits(x, &quot;SpatRaster&quot;) || inherits(x, &quot;SpatVector&quot;))  if (terra::crs(x) == &quot;&quot; || is.na(terra::crs(x))) {    stop(&quot;&apos;x&apos; does not have a CRS to project&quot;)  }  if (inherits(x, &quot;SpatRaster&quot;)) {    a &lt;- terra::as.polygons(x * 0)  }  if (inherits(x, &quot;SpatVector&quot;) &#038;&#038; length(x) &gt; 1) {    a &lt;- terra::aggregate(x)  } else {    a &lt;- x  }  if (!isTRUE(terra::is.lonlat(a))) {    a &lt;- terra::project(a, &quot;EPSG:4326&quot;)  }  centr &lt;- terra::crds(terra::centroids(a))  proj &lt;- tolower(proj)  proj &lt;- sub(&quot;+proj=&quot;, &quot;&quot;, proj, fixed = TRUE)  # in case user input prefix  CRS &lt;- paste(    paste0(&quot;+proj=&quot;, proj),    paste0(&quot;+lat_0=&quot;, centr[2]),    paste0(&quot;+lon_0=&quot;, centr[1])  )  tryCatch(terra::project(x, CRS),           error = function(e)             stop(&quot;&apos;proj&apos; is invalid&quot;))}</pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Here is a use case (compared with the default behaviour), using e.g. Lambert Azimuthal Equal-Area projection:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
library(terra)library(geodata)countries &lt;- geodata::world(path = tempdir())map &lt;- subset(countries, countries$NAME_0 %in% c(&quot;New Zealand&quot;, &quot;Australia&quot;))par(mfrow = c(1, 3))plot(map, main = &quot;Unprojected&quot;)plot(project(map, &quot;+proj=laea&quot;), main = &quot;Projected, default-centered&quot;)plot(projectCentered(map, &quot;laea&quot;), main = &quot;Projected, map-centered&quot;)</pre>
		</div>
	</div>
</div>


<figure class="wp-block-image size-large"><a href="https://i1.wp.com/modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?ssl=1" rel="nofollow" target="_blank"><img loading="lazy" data-attachment-id="3596" data-permalink="https://modtools.wordpress.com/2026/10/22/automatically-center-a-projection-on-the-plotted-map/projectcentered-3/" data-orig-file="https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png" data-orig-size="1704,528" data-comments-opened="1" data-image-title="projectcentered" data-image-description="" data-image-caption="" data-large-file="https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=584" src="https://i1.wp.com/modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=450&#038;ssl=1" alt="" class="wp-image-3596" srcset_temp="https://i1.wp.com/modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=450&#038;ssl=1 1024w, https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=150 150w, https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=300 300w, https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=768 768w, https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=1440 1440w, https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png 1704w" sizes="(max-width: 1024px) 100vw, 1024px" data-recalc-dims="1" /></a></figure>



<p class="wp-block-paragraph">This is meant to be used for visualization purposes. For computing e.g. areas or distances, it is better to use unprojected layers, with longitude-latitude coordinates in degrees (making sure that spherical geometry or geodesic algorithms are being used), so that the Earth’s curvature is taken into account.</p>



<p class="wp-block-paragraph">Note that I just plotted the bare minimum to illustrate what the ‘projectCentered’ function does. Maps can be made to look much nicer — even with just terra + base R plots, with no added package dependencies!</p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://modtools.wordpress.com/2026/10/22/automatically-center-a-projection-on-the-plotted-map/"> modTools</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/automatically-center-a-projection-on-the-plotted-map/">Automatically center a projection on the plotted map</a>]]></content:encoded>
					
		
		<enclosure url="https://2.gravatar.com/avatar/e1a349777384e70d2eb7ee3f26788864b1c880ec271cfc1cb8f9d8cc3000cd11?s=96&#038;d=retro&#038;r=G" length="0" type="" />
<enclosure url="https://modtools.wordpress.com/wp-content/uploads/2026/10/projectcentered-2.png?w=1024" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404393</post-id>	</item>
		<item>
		<title>rOpenSci News Digest, October 2026</title>
		<link>https://www.r-bloggers.com/2026/10/ropensci-news-digest-october-2026/</link>
		
		<dc:creator><![CDATA[rOpenSci]]></dc:creator>
		<pubDate>Thu, 22 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://ropensci.org/blog/2026/10/22/news-october-2026/</guid>

					<description><![CDATA[<p>Dear rOpenSci friends, it’s time for our monthly news roundup!<br />
You can read this post on our blog.<br />
Now let’s dive into the activity at and around rOpenSci!</p>
<p>rOpenSci HQ</p>
<p>Team update: thank you Mark Padgham!<br />
Mark Padgham is leaving rOpenSc...</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/ropensci-news-digest-october-2026/">rOpenSci News Digest, October 2026</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://ropensci.org/blog/2026/10/22/news-october-2026/"> rOpenSci - open tools for open science</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<!-- Before sending DELETE THE INDEX_CACHE and re-knit! -->
<p>Dear rOpenSci friends, it’s time for our monthly news roundup!
You can read this post <a href="https://ropensci.org/blog/2026/10/22/news-october-2026" rel="nofollow" target="_blank">on our blog</a>.
Now let’s dive into the activity at and around rOpenSci!</p>
<h2>
rOpenSci HQ
</h2><h3>
Team update: thank you Mark Padgham!
</h3><p>Mark Padgham is leaving rOpenSci at the end of October, but his impact on rOpenSci will remain.</p>
<p>Mark joined rOpenSci as a staff member in December 2019 to help us expand software peer review to statistical topics.
However, Mark’s impact on rOpenSci has gone far beyond this one project.
Mark has not only shaped how rOpenSci reviews statistical software, but has also built much of the tooling that supports the peer review processes, and R packages which not only support but also strengthen our community.
If you have submitted a package to rOpenSci, reviewed or edited one, you have almost certainly used something Mark made!</p>
<p>Read more about his work in our <a href="https://ropensci.org/blog/2026/10/21/mark-building-machinery-software-review/" rel="nofollow" target="_blank">blog post</a>, and consider <a href="https://ropensci.org/events/marks-celebration/" rel="nofollow" target="_blank">joining our farewell celebration</a> on Wednesday, October 28th at 15h00 Europe Central.</p>
<h3>
Champions Program update
</h3><p>Our current 2026-2027 cohort is working on their projects around package development and package peer-review.
We celebrated our first cohort meeting (across two different gatherings to accommodate time zones) where participants shared their progress and got feedback from other Champions.</p>
<p>Outreach activities are also under way: <a href="https://ropensci.org/author/linda-cabrera-orellana/" rel="nofollow" target="_blank">Linda Cabrera</a>, assisted by her mentor <a href="https://ropensci.org/author/luis-d.-verde-arregoitia/" rel="nofollow" target="_blank">Luis Verde</a>, conducted her outreach activity by leading a workshop on R Projects with RStudio and GitHub for the <a href="https://see-ec.org/" rel="nofollow" target="_blank">Ecuadorian Statistical Society</a>, where she had the opportunity to share information about rOpenSci and our programs with the attendees.</p>
<p>The 2025–2026 cohort wrapped up their journey with a closing community call, <a href="https://ropensci.org/es/commcalls/mas-alla-del-codigo-2026/" rel="nofollow" target="_blank"><strong>Más Allá del Código: muestra abierta de los proyectos de nuestros campeon(a|e)s (Beyond the Code: an open showcase of our Champions’ Projects)</strong></a>.
At the call, the Champions presented their final projects, from creating, improving, and reviewing R packages to the outreach work that brought those projects to their communities.
They also shared what their projects led to, including scholarships, new collaborations, and professional growth.
The recording and subtitles, all in Spanish, have been <a href="https://ropensci.org/es/commcalls/mas-alla-del-codigo-2026/" rel="nofollow" target="_blank">published</a>.</p>
<p>Several Champions and mentors will join <a href="https://latinr.org/cronograma/" rel="nofollow" target="_blank">LatinR 2026 in Medellín, Colombia</a>, to present their work during the program and participate in <a href="https://latinr.org/cronograma/hackear/" rel="nofollow" target="_blank">HackeaR, the hackathon event</a> where we will work on issues, translations, and Pull Requests on base R and on rOpenSci packages and resources.</p>
<p><a href="https://www.eventbrite.cl/e/latinr-2026-conferencia-latinoamericana-sobre-uso-de-r-en-id-tickets-1998690018649" rel="nofollow" target="_blank">If you are around, come and join us!</a></p>
<h3>
R-Universe updates
</h3><p><img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f9ec.png" alt="🧬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> We have made good progress on the <a href="https://ropensci.org/blog/2026/04/08/r-universe-bioc/" rel="nofollow" target="_blank">migration of Bioconductor to R-Universe infrastructure</a>, see also <a href="https://talks.seandavis.net/2026-10-01-bioc-tab/#/" rel="nofollow" target="_blank">this presentation by Sean Davis</a>.
Among other things, packages from the Bioconductor “experiment”, “workflows” and “books” repositories have been added to <a href="https://bioc.r-universe.dev/" rel="nofollow" target="_blank">bioc.r-universe.dev</a> and <a href="https://bioc-release.r-universe.dev/" rel="nofollow" target="_blank">bioc-release.r-universe.dev</a>.
To support this, R-Universe now has a special category of “data” packages, which get a higher size limit but are only built and checked on Linux.</p>
<p><img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> With about 3000 packages, Bioconductor is by far the largest universe, so along the way we made dozens of performance improvements to the frontend and server, making pages and repositories load faster for every universe.
Stay tuned for a dedicated blog post on the state of the Bioconductor transition before the end of the year!</p>
<h2>
Quinceañera
</h2><p>We continue to uncover the stories of the people behind rOpenSci.
This month, Mauro Lepore <a href="https://ropensci.org/blog/2026/09/29/celebrating-ropensci/" rel="nofollow" target="_blank">tells us about his journey with rOpenSci</a> and shares lessons learned that can be applied beyond our community.</p>
<p>We’re also sharing a <a href="https://ropensci.org/blog/2026/10/20/15yo-events/" rel="nofollow" target="_blank">recap of the synchronous events we held</a>, where we discussed the past, present, and future of rOpenSci with the people who joined us to celebrate these 15 years.</p>
<p>Stay tuned for more rOpenSci’s stories.</p>
<h3>
Coworking
</h3><p>Read <a href="https://ropensci.org/blog/2023/06/21/coworking/" rel="nofollow" target="_blank">all about coworking</a>!</p>
<ul>
<li>Tuesday November 3rd, 09:00 Australia Western (01:00 UTC) <a href="https://ropensci.org/events/coworking-2026-11/" rel="nofollow" target="_blank">“Climate Science in R”</a>, with <a href="https://ropensci.org/author/steffi-lazerte" rel="nofollow" target="_blank">Steffi LaZerte</a> and co-host <a href="https://ropensci.org/author/elio-campitelli/" rel="nofollow" target="_blank">Elio Campitelli</a>.
<ul>
<li>Explore how R is used to study the climate</li>
<li>Meet co-host, Elio Campitelli, and discuss Climate Science in R</li>
</ul>
</li>
<li>Tuesday December 8th<sup>*</sup>, 14:00 Europe Central (12:00 UTC) <a href="https://ropensci.org/events/coworking-2026-12/" rel="nofollow" target="_blank">“Code Linting in R”</a>, with <a href="https://ropensci.org/author/steffi-lazerte" rel="nofollow" target="_blank">Steffi LaZerte</a> and co-host <a href="https://ropensci.org/author/etienne-bacher/" rel="nofollow" target="_blank">Etienne Bacher</a>.
<ul>
<li>Read up on Code Linting and apply some linters to your R code</li>
<li>Meet co-host, Etienne Bacher, and discuss code linting in general, or flir and Jarl in particular<br>
* Note that December coworking is a week later than usual</li>
</ul>
</li>
</ul>
<p>And remember, you can always cowork independently on work related to R, work on packages that tend to be neglected, or work on what ever you need to get done!</p>
<h2>
Software <img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" />
</h2><p>The following three packages recently became a part of our software suite:</p>
<ul>
<li>
<p><a href="https://docs.ropensci.org/ibger" rel="nofollow" target="_blank">ibger</a>, developed by André Leite together with Marcos Wasiliew, Hugo Vasconcelos, Carlos Amorim, and Diogo Bezerra: Tidyverse-friendly interface to the Brazilian Institute of Geography and Statistics (IBGE) aggregate data API <a href="https://servicodados.ibge.gov.br/api/docs/agregados?versao=3" rel="nofollow" target="_blank">https://servicodados.ibge.gov.br/api/docs/agregados?versao=3</a>.
Query aggregates, variables, localities, periods, and metadata from surveys and censuses conducted by IBGE.
It has been <a href="https://github.com/ropensci/software-review/issues/787" rel="nofollow" target="_blank">reviewed</a> by Allan Quadros and Diana García-Cortés.</p>
</li>
<li>
<p><a href="https://docs.ropensci.org/rcrisp" rel="nofollow" target="_blank">rcrisp</a>, developed by Claudiu Forgaci together with Francesco Nattino: Provides tools to automate the morphological delineation of riverside urban areas based on a method introduced in Forgaci (2018) <a href="https://doi.org/10.7480/abe.2018.31" rel="nofollow" target="_blank">https://doi.org/10.7480/abe.2018.31</a>.
Delineation entails the identification of corridor boundaries, segmentation of the corridor, and delineation of the river space using two-dimensional spatial information from street network data and digital elevation data in a projected CRS.
The resulting delineation can be used to characterise spatial phenomena that can be related to the river as a central element.
It has been <a href="https://github.com/ropensci/software-review/issues/718" rel="nofollow" target="_blank">reviewed</a> by Mark Padgham and Anatolii Tsyplenkov.</p>
</li>
<li>
<p><a href="https://docs.ropensci.org/coevolve" rel="nofollow" target="_blank">coevolve</a>, developed by Scott Claessens together with Erik Ringen: Fit Bayesian generalized dynamic phylogenetic models using Stan or JAX.
The package allows an arbitrary number of variables with different response distributions to coevolve on a phylogenetic tree via an Ornstein-Uhlenbeck evolutionary process, allowing users to assess directionality between coevolving variables.
The optional JAX backend enables high-performance sampling with NumPyro and potential GPU acceleration via reticulate.
It has been <a href="https://github.com/ropensci/software-review/issues/717" rel="nofollow" target="_blank">reviewed</a> by Rob Moss and Krzysztof Bartoszek.</p>
</li>
</ul>
<p>Discover <a href="https://ropensci.org/packages" rel="nofollow" target="_blank">more packages</a>, read more about <a href="https://ropensci.org/software-review" rel="nofollow" target="_blank">Software Peer Review</a>.</p>
<h3>
New versions
</h3><p>The following sixteen packages have had an update since the last newsletter: <a href="https://docs.ropensci.org/coevolve" title="Fit Bayesian Generalized Dynamic Phylogenetic Models using Stan or JAX" rel="nofollow" target="_blank">coevolve</a> (<a href="https://github.com/ropensci/coevolve/releases/tag/v1.2.1" rel="nofollow" target="_blank"><code>v1.2.1</code></a>), <a href="https://docs.ropensci.org/ruODK" title="An R Client for the ODK Central API" rel="nofollow" target="_blank">ruODK</a> (<a href="https://github.com/ropensci/ruODK/releases/tag/v1.6.3" rel="nofollow" target="_blank"><code>v1.6.3</code></a>), <a href="https://docs.ropensci.org/pkgcheck" title="Package Checks for rOpenSci" rel="nofollow" target="_blank">pkgcheck</a> (<a href="https://github.com/ropensci-review-tools/pkgcheck/releases/tag/v0.3.4" rel="nofollow" target="_blank"><code>v0.3.4</code></a>), <a href="https://docs.ropensci.org/rcrisp" title="Automate the Delineation of Urban River Spaces" rel="nofollow" target="_blank">rcrisp</a> (<a href="https://github.com/CityRiverSpaces/rcrisp/releases/tag/v1.0.0" rel="nofollow" target="_blank"><code>v1.0.0</code></a>), <a href="https://docs.ropensci.org/pkgstats" title="Metrics of R Packages" rel="nofollow" target="_blank">pkgstats</a> (<a href="https://github.com/ropensci-review-tools/pkgstats/releases/tag/v0.2.5" rel="nofollow" target="_blank"><code>v0.2.5</code></a>), <a href="https://docs.ropensci.org/openalexR" title="Getting Bibliographic Records from OpenAlex Database Using DSL API" rel="nofollow" target="_blank">openalexR</a> (<a href="https://github.com/ropensci/openalexR/releases/tag/v3.1.2" rel="nofollow" target="_blank"><code>v3.1.2</code></a>), <a href="https://docs.ropensci.org/webchem" title="Chemical Information from the Web" rel="nofollow" target="_blank">webchem</a> (<a href="https://github.com/ropensci/webchem/releases/tag/v2.0.0" rel="nofollow" target="_blank"><code>v2.0.0</code></a>), <a href="https://docs.ropensci.org/ReLTER" title="An Interface for the eLTER Community" rel="nofollow" target="_blank">ReLTER</a> (<a href="https://github.com/ropensci/ReLTER/releases/tag/V3.1.2" rel="nofollow" target="_blank"><code>V3.1.2</code></a>), <a href="https://docs.ropensci.org/rerddap" title="General Purpose Client for ERDDAP&#x2122; Servers" rel="nofollow" target="_blank">rerddap</a> (<a href="https://github.com/ropensci/rerddap/releases/tag/v1.3.1" rel="nofollow" target="_blank"><code>v1.3.1</code></a>), <a href="https://docs.ropensci.org/ibger" title="Access the IBGE Aggregate Data API from R" rel="nofollow" target="_blank">ibger</a> (<a href="https://github.com/ropensci/ibger/releases/tag/v0.2.1" rel="nofollow" target="_blank"><code>v0.2.1</code></a>), <a href="https://docs.ropensci.org/rgbif" title="Interface to the Global Biodiversity Information Facility API" rel="nofollow" target="_blank">rgbif</a> (<a href="https://github.com/ropensci/rgbif/releases/tag/v3.9.0" rel="nofollow" target="_blank"><code>v3.9.0</code></a>), <a href="https://docs.ropensci.org/rglobi" title="Interface to Global Biotic Interactions" rel="nofollow" target="_blank">rglobi</a> (<a href="https://github.com/ropensci/rglobi/releases/tag/v0.3.5" rel="nofollow" target="_blank"><code>v0.3.5</code></a>), <a href="https://docs.ropensci.org/lakefetch" title="Calculate Fetch and Wave Exposure for Lake Sampling Points" rel="nofollow" target="_blank">lakefetch</a> (<a href="https://github.com/ropensci/lakefetch/releases/tag/v0.1.14" rel="nofollow" target="_blank"><code>v0.1.14</code></a>), <a href="https://docs.ropensci.org/visdat" title="Preliminary Visualisation of Data" rel="nofollow" target="_blank">visdat</a> (<a href="https://github.com/ropensci/visdat/releases/tag/v0.6.1" rel="nofollow" target="_blank"><code>v0.6.1</code></a>), <a href="https://docs.ropensci.org/weatherOz" title="An API Client for Australian Weather and Climate Data Resources" rel="nofollow" target="_blank">weatherOz</a> (<a href="https://github.com/ropensci/weatherOz/releases/tag/v3.0.1" rel="nofollow" target="_blank"><code>v3.0.1</code></a>), and <a href="https://docs.ropensci.org/nlrx" title="Setup, Run and Analyze NetLogo Model Simulations from R via XML" rel="nofollow" target="_blank">nlrx</a> (<a href="https://github.com/ropensci/nlrx/releases/tag/v0.4.6" rel="nofollow" target="_blank"><code>v0.4.6</code></a>).</p>
<h2>
Software Peer Review
</h2><p>There are seventeen recently closed and active submissions and 4 submissions on hold.
Issues are at different stages:</p>
<ul>
<li>
<p>Three at <a href="https://github.com/ropensci/software-review/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%226/approved%22" rel="nofollow" target="_blank">‘6/approved’</a>:</p>
<ul>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/787" rel="nofollow" target="_blank">ibger</a>, Access the IBGE Aggregate Data API from R.
Submitted by <a href="https://castlab.org/" rel="nofollow" target="_blank">Andre Leite Wanderley</a>.</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/718" rel="nofollow" target="_blank">rcrisp</a>, Automate the Delineation of Urban River Spaces.
Submitted by <a href="https://github.com/cforgaci" rel="nofollow" target="_blank">Claudiu Forgaci</a>.
(Stats).</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/717" rel="nofollow" target="_blank">coevolve</a>, Fit Bayesian Generalized Dynamic Phylogenetic Models using Stan.
Submitted by <a href="https://scottclaessens.github.io/" rel="nofollow" target="_blank">Scott Claessens</a>.
(Stats).</p>
</li>
</ul>
</li>
<li>
<p>One at <a href="https://github.com/ropensci/software-review/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%225/awaiting-reviewer(s)-response%22" rel="nofollow" target="_blank">‘5/awaiting-reviewer(s)-response’</a>:</p>
<ul>
<li><a href="https://github.com/ropensci/software-review/issues/744" rel="nofollow" target="_blank">RAQSAPI</a>, A Simple Interface to the US EPA Air Quality System Data Mart API.
Submitted by <a href="https://github.com/mccroweyclinton-EPA" rel="nofollow" target="_blank">mccroweyclinton-EPA</a>.</li>
</ul>
</li>
<li>
<p>Two at <a href="https://github.com/ropensci/software-review/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%224/review(s)-in-awaiting-changes%22" rel="nofollow" target="_blank">‘4/review(s)-in-awaiting-changes’</a>:</p>
<ul>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/799" rel="nofollow" target="_blank">camtrapReport</a>, Camera-Trap Report Generator.
Submitted by <a href="https://www.wur.nl/en/persons/e-elham-ebrahimi" rel="nofollow" target="_blank">Elham Ebrahimi</a>.</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/785" rel="nofollow" target="_blank">nert</a>, Curated Access to TERN Environmental Raster Data.
Submitted by <a href="https://scholar.google.com.au/citations?user=zG1uKrcAAAAJ&#038;hl=en" rel="nofollow" target="_blank">Max Moldovan</a>.</p>
</li>
</ul>
</li>
<li>
<p>Two at <a href="https://github.com/ropensci/software-review/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%223/reviewer(s)-assigned%22" rel="nofollow" target="_blank">‘3/reviewer(s)-assigned’</a>:</p>
<ul>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/774" rel="nofollow" target="_blank">tezr</a>, Access Thesis Metadata from Turkiye’s National Thesis Center.
Submitted by <a href="https://github.com/emraher" rel="nofollow" target="_blank">Emrah Er</a>.</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/769" rel="nofollow" target="_blank">rfastlowess</a>, High-Performance LOWESS Smoothing for R.
Submitted by <a href="https://github.com/thisisamirv" rel="nofollow" target="_blank">Amir Valizadeh</a>.
(Stats).</p>
</li>
</ul>
</li>
<li>
<p>Two at <a href="https://github.com/ropensci/software-review/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%222/seeking-reviewer(s)%22" rel="nofollow" target="_blank">‘2/seeking-reviewer(s)’</a>:</p>
<ul>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/808" rel="nofollow" target="_blank">corila</a>, Sparse modelling with grouped and correlated features allowing for privileged information.
Submitted by <a href="https://rauschenberger.github.io/" rel="nofollow" target="_blank">Armin Rauschenberger</a>.
(Stats).</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/775" rel="nofollow" target="_blank">grumpy</a>, Read NumPy .npy and .npz Files.
Submitted by <a href="https://hugogruson.fr/" rel="nofollow" target="_blank">Hugo Gruson</a>.</p>
</li>
</ul>
</li>
<li>
<p>Seven at <a href="https://github.com/ropensci/software-review/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%221/editor-checks%22" rel="nofollow" target="_blank">‘1/editor-checks’</a>:</p>
<ul>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/811" rel="nofollow" target="_blank">ocean3d</a>, Three-Dimensional Marine Spatial Analysis.
Submitted by <a href="https://jmatsushiba.com/" rel="nofollow" target="_blank">Jay Matsushiba</a>.</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/809" rel="nofollow" target="_blank">emodnet.wcs</a>, Access EMODnet Web Coverage Service data through R.
Submitted by <a href="https://masalmon.eu/" rel="nofollow" target="_blank">Maëlle Salmon</a>.</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/807" rel="nofollow" target="_blank">qualtdict</a>, Generate Variable Dictionaries and Labelled Survey Data for.
Submitted by <a href="https://github.com/lyh970817" rel="nofollow" target="_blank">lyh970817</a>.</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/805" rel="nofollow" target="_blank">tarpolyglot</a>, Run Python, Julia, Rust, and C++ Inside targets Pipeline Steps.
Submitted by <a href="https://github.com/Pierre9344" rel="nofollow" target="_blank">Pierre9344</a>.</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/786" rel="nofollow" target="_blank">seasight</a>, Tools for Seasonal Adjustment Diagnostics and Reporting.
Submitted by <a href="https://p-wegmueller.github.io/" rel="nofollow" target="_blank">Philipp Wegmueller</a>.
(Stats).</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/777" rel="nofollow" target="_blank">OptSurvCutR</a>, Optimal Survival Cut-Point Discovery for Time-to-Event Analysis with OptSurvCutR.
Submitted by <a href="https://github.com/paytonyau" rel="nofollow" target="_blank">Payton Yau</a>.
(Stats).</p>
</li>
<li>
<p><a href="https://github.com/ropensci/software-review/issues/766" rel="nofollow" target="_blank">HydraR</a>, Stateful Agentic Orchestration for Scientific Reproducibility.
Submitted by <a href="https://www.mq.edu.au/research/research-centres-groups-and-facilities/facilities/australian-proteome-analysis-facility" rel="nofollow" target="_blank">Ignatius Pang</a>.</p>
</li>
</ul>
</li>
</ul>
<p>Find out more about <a href="https://ropensci.org/software-review" rel="nofollow" target="_blank">Software Peer Review</a> and how to get involved.</p>
<h2>
On the blog
</h2><!-- Do not forget to rebase your branch! -->
<h3>
Software Review
</h3><ul>
<li>
<p><a href="https://ropensci.org/blog/2026/09/29/celebrating-ropensci" rel="nofollow" target="_blank">Celebrating the rOpenSci Community</a> by Mauro Lepore.
rOpenSci turns 15 and I’ve been a part of this community for a good chunk of that time.
Here I share some memories and the lessons I learned.
Other languages: <a href='https://ropensci.org/es/blog/2026/09/29/celebrando-ropensci' lang='es' rel="nofollow" target="_blank">Celebrando a la comunidad de rOpenSci (es)</a>.</p>
</li>
<li>
<p><a href="https://ropensci.org/blog/2026/10/20/15yo-events" rel="nofollow" target="_blank">Celebrating 15 Years of rOpenSci: Then, Now &#038; Next</a> by Yanina Bellini Saibene, Steffi LaZerte, and Noam Ross.
Reflections and ideas shared by participants across the two special gatherings named <strong>“rOpenSci Community Celebration: Then, Now &#038; Next”</strong>.</p>
</li>
<li>
<p><a href="https://ropensci.org/blog/2026/10/21/mark-building-machinery-software-review" rel="nofollow" target="_blank">Mark Padgham: Building the Machinery of Software Peer Review</a> by The rOpenSci Team.
Mark Padgham is leaving rOpenSci at the end of October, and we want to take this moment to recognize his impact and say thank you for helping to shape rOpenSci.</p>
</li>
</ul>
<h2>
Calls for contributions
</h2><h3>
Calls for maintainers
</h3><p>If you’re interested in maintaining any of the R packages below, you might enjoy reading our blog post <a href="https://ropensci.org/blog/2023/02/07/what-does-it-mean-to-maintain-a-package/" rel="nofollow" target="_blank">What Does It Mean to Maintain a Package?</a>.</p>
<ul>
<li>
<p><a href="https://docs.ropensci.org/suppdata" rel="nofollow" target="_blank">suppdata</a>, Grabbing SUPPlementary DATA in R.
<a href="https://github.com/ropensci/suppdata/issues/59" rel="nofollow" target="_blank">Issue for volunteering</a>.</p>
</li>
<li>
<p><a href="https://docs.ropensci.ghql/" rel="nofollow" target="_blank">ghql</a>, GraphQL R client.
<a href="https://github.com/ropensci/ghql/issues/40" rel="nofollow" target="_blank">Issue for volunteering</a>.</p>
</li>
</ul>
<h3>
Calls for contributions
</h3><p>Refer to our <a href="https://ropensci.org/help-wanted/" rel="nofollow" target="_blank">help wanted page</a> – before opening a PR, we recommend asking in the issue whether help is still needed.</p>
<h2>
Package development corner
</h2><p>Some useful information for R package developers.
<img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f440.png" alt="👀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<h3>
A Safe Space for Learning How to Make Pull Requests
</h3><p>Jonathan Carroll created and wrote about a <a href="https://jcarroll.com.au/2024/10/01/a-safe-space-for-learning-how-to-make-pull-requests/" rel="nofollow" target="_blank">package repository</a> that is open to PRs by newbies.
It’s a playground for learning how to create pull requests.
It’s also an inspiration for package maintainers who want to encourage new contributors!</p>
<blockquote>
<p><em>“My target audience for this repository is people who wish they had somewhere to send a real pull request because they don’t yet feel comfortable participating in open source software.”</em></p>
</blockquote>
<h3>
Check for global state changes in your testthat tests
</h3><p>Did you know about <a href="https://testthat.r-lib.org/reference/set_state_inspector.html" rel="nofollow" target="_blank"><code>testthat::set_state_inspector()</code></a>?
If you use it by registering some things you don’t want to change (environment variables for instance), it is run before and after each test, and reports whether there’s any discrepancy in the global state.
Here’s how it’s used in <a href="https://github.com/ropensci/vcr/blob/b7c440ee4e1c8d7e78cce3f274c3da788f99d7b3/tests/testthat/helper-vcr.R#L101-L111" rel="nofollow" target="_blank">vcr</a>:</p>
<pre>testthat::set_state_inspector(\() {
 temp_files &lt;- dir(tempdir())
 temp_files &lt;- temp_files[!grepl(&quot;^callr&quot;, temp_files)]
 temp_files &lt;- temp_files[!grepl(&quot;^webfakes&quot;, temp_files)]

 list(
 temp_files = temp_files,
 wd_files = dir(),
 vcr_config = the$config
 )
})
</pre><p>Steffi LaZerte noticed the function in Hadley Wickham’s and Jenny Bryan’s <a href="https://posit-conf-2026.github.io/modern-r-workflow/" rel="nofollow" target="_blank">workshop materials</a> that Mauro Lepore shared in our Slack workspace.</p>
<h3>
Using <code>just</code> to run a subset of tests
</h3><p>Speaking of tests… Did you know that testthat’s test function have a <code>filter</code> argument to only run tests that match a given string?
Etienne Bacher wrote a <a href="https://www.etiennebacher.com/posts/2026-10-15-using-just-with-test-files/" rel="nofollow" target="_blank">nice little post</a> about using the <code>just</code> command-line tool to generate aliases for easily running a subset of tests using this filter.</p>
<p>The post also mentions other use cases of <a href="https://github.com/casey/just" rel="nofollow" target="_blank"><code>just</code></a>, e.g. defining <code>just lint</code> to run <a href="https://jarl.etiennebacher.com/" rel="nofollow" target="_blank">Jarl</a> on the current directory.</p>
<h3>
Git hashes
</h3><p>Are you aware that Git plans to change its content hashing algorithm?
Here’s a <a href="https://blog.gitbutler.com/git-3-sha-256" rel="nofollow" target="_blank">good post</a> by Scott Chacon, the author of the Pro Git book, on why it is important and might be problematic.</p>
<h2>
Last words
</h2><p>Thanks for reading!
If you want to get involved with rOpenSci, check out our <a href="https://contributing.ropensci.org/" rel="nofollow" target="_blank">Contributing Guide</a>.
This guide will help direct you to the right place, whether you want to make code contributions, non-code contributions, or contribute in other ways such as through sharing use cases.
You can also support our work through <a href="https://ropensci.org/donate" rel="nofollow" target="_blank">donations</a>.</p>
<p>If you haven’t subscribed to our newsletter yet, you can <a href="https://ropensci.org/news/" rel="nofollow" target="_blank">do so though our signup form</a>.
Until it’s time for our next newsletter, you can keep in touch with us through our <a href="https://ropensci.org/" rel="nofollow" target="_blank">website</a>, <a href="https://hachyderm.io/@rOpenSci" rel="nofollow" target="_blank">Mastodon</a>, or <a href="https://www.linkedin.com/company/ropensci/" rel="nofollow" target="_blank">LinkedIn</a>.
See you soon!</p>
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://ropensci.org/blog/2026/10/22/news-october-2026/"> rOpenSci - open tools for open science</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/ropensci-news-digest-october-2026/">rOpenSci News Digest, October 2026</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404383</post-id>	</item>
		<item>
		<title>Hybrid Machine Learning &#038; Financial Econometrics: Building Volatility-Aware Dynamic Forecast Bands for Bitcoin</title>
		<link>https://www.r-bloggers.com/2026/10/hybrid-machine-learning-financial-econometrics-building-volatility-aware-dynamic-forecast-bands-for-bitcoin/</link>
		
		<dc:creator><![CDATA[Selcuk Disci]]></dc:creator>
		<pubDate>Wed, 21 Oct 2026 11:37:45 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">http://datageeek.com/?p=12961</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> When forecasting high-volatility financial assets like Bitcoin (BTC-USD), standard point-prediction models—whether classical ARIMA or deep neural networks—often fall short. A point estimate tells you where the model expects the price to go, but it completely ignores market regime shifts and changing risk levels. In this article, we construct ...</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/hybrid-machine-learning-financial-econometrics-building-volatility-aware-dynamic-forecast-bands-for-bitcoin/">Hybrid Machine Learning & Financial Econometrics: Building Volatility-Aware Dynamic Forecast Bands for Bitcoin</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://datageeek.com/2026/10/21/hybrid-machine-learning-financial-econometrics-building-volatility-aware-dynamic-forecast-bands-for-bitcoin/"> DataGeeek</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p class="wp-block-paragraph">When forecasting high-volatility financial assets like Bitcoin (BTC-USD), standard point-prediction models—whether classical ARIMA or deep neural networks—often fall short. A point estimate tells you <em>where</em> the model expects the price to go, but it completely ignores market regime shifts and changing risk levels.</p>



<p class="wp-block-paragraph">In this article, we construct an end-to-end forecasting pipeline in R that combines <strong>Machine Learning (Multi-Layer Perceptron via <code>brulee</code> and <code>tidymodels</code>)</strong> for directional trend prediction with <strong>Financial Econometrics (GJR-GARCH via <code>rugarch</code>)</strong> to generate dynamic, volatility-adjusted confidence bounds.</p>



<h2 class="wp-block-heading">The Architecture Overview</h2>



<p class="wp-block-paragraph">Instead of attempting to forecast level prices directly—which introduces non-stationarity issues—our pipeline operates on <strong>log returns</strong> and reconstructs price trajectories dynamically.</p>



<figure data-wp-context="{"imageId":"6ad8a491b70da"}" data-wp-interactive="core/image" data-wp-key="6ad8a491b70da" class="wp-block-image size-large wp-lightbox-container"><img loading="lazy" data-attachment-id="12969" data-permalink="https://datageeek.com/2026/10/21/hybrid-machine-learning-financial-econometrics-building-volatility-aware-dynamic-forecast-bands-for-bitcoin/image-141/" data-orig-file="https://datageeek.com/wp-content/uploads/2026/10/image.png" data-orig-size="981,438" data-comments-opened="1" data-image-title="image" data-image-description="" data-image-caption="" data-large-file="https://i2.wp.com/datageeek.com/wp-content/uploads/2026/10/image.png?w=450&#038;ssl=1" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://i2.wp.com/datageeek.com/wp-content/uploads/2026/10/image.png?w=450&#038;ssl=1" alt="" class="wp-image-12969" srcset_temp="https://datageeek.com/wp-content/uploads/2026/10/image.png 981w, https://datageeek.com/wp-content/uploads/2026/10/image.png?w=150 150w, https://datageeek.com/wp-content/uploads/2026/10/image.png?w=300 300w, https://datageeek.com/wp-content/uploads/2026/10/image.png?w=768 768w" sizes="(max-width: 981px) 100vw, 981px" data-recalc-dims="1" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 class="wp-block-heading">Key Architectural Decisions &#038; Methodological Rationale</h2>



<h3 class="wp-block-heading">1. Modeling Log Returns Instead of Level Prices</h3>



<p class="wp-block-paragraph">Directly predicting raw prices often creates spurious regression results because price series are non-stationary. By transforming the price series to <strong>log returns</strong>:<br></p>



<div class="wp-block-math"><math display="block"><semantics><mrow><msub><mi>r</mi><mi>t</mi></msub><mo>=</mo><mrow><mi>ln</mi><mo>⁡</mo></mrow><mrow><mo fence="true" form="prefix">(</mo><mfrac><msub><mi>P</mi><mi>t</mi></msub><msub><mi>P</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow></msub></mfrac><mo fence="true" form="postfix">)</mo></mrow></mrow><annotation encoding="application/x-tex">r_t = \ln\left(\frac{P_t}{P_{t-1}}\right)</annotation></semantics></math></div>





<p class="wp-block-paragraph">This transformation achieves stationarity, making the target variable suitable for neural network training. Factual price predictions are reconstructed at test time via cumulative exponentiation:<br></p>



<div class="wp-block-math"><math display="block"><semantics><mrow><msub><mover><mi>P</mi><mo stretchy="false">^</mo></mover><mi>t</mi></msub><mo>=</mo><msub><mi>P</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow></msub><mo>×</mo><mrow><mi>exp</mi><mo>⁡</mo></mrow><mo form="prefix" stretchy="false">(</mo><msub><mover><mi>r</mi><mo stretchy="false">^</mo></mover><mi>t</mi></msub><mo form="postfix" stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\hat{P}_t = P_{t-1} \times \exp(\hat{r}_t)</annotation></semantics></math></div>



<h3 class="wp-block-heading">2. Deep Learning for Trend: Multi-Layer Perceptron (<code>brulee</code>)</h3>



<p class="wp-block-paragraph">We use a 2-layer MLP architecture <code>(32, 16)</code> initialized via the <code>tidymodels</code> framework.</p>



<ul class="wp-block-list">
<li><strong>Lag Features</strong>: We supply 20 days of lagged log returns to capture short- to medium-term autocorrelation.</li>



<li><strong>Normalizations &#038; Dropping Zero Variance</strong>: Handled within a clean <code>recipes</code> pipeline to prevent data leakage</li>
</ul>



<h3 class="wp-block-heading">3. Econometric Volatility Modeling: GJR-GARCH(1,1) on Neural Network Residuals</h3>



<p class="wp-block-paragraph">Machine learning models capture the conditional mean, but their residuals,                                                             <math data-latex=" \epsilon_t = r_t - \hat{r}_t"><semantics><mrow><msub><mi>ϵ</mi><mi>t</mi></msub><mo>=</mo><msub><mi>r</mi><mi>t</mi></msub><mo>−</mo><msub><mover><mi>r</mi><mo stretchy="false">^</mo></mover><mi>t</mi></msub></mrow><annotation encoding="application/x-tex"> \epsilon_t = r_t – \hat{r}_t</annotation></semantics></math> , still contain <strong>volatility clustering</strong>—periods of high turbulence followed by high turbulence, and calm periods followed by calm.</p>



<p class="wp-block-paragraph">To model this conditional variance (<math data-latex="\sigma_t^2"><semantics><msubsup><mi>σ</mi><mi>t</mi><mn>2</mn></msubsup><annotation encoding="application/x-tex">\sigma_t^2</annotation></semantics></math>), we fit a <strong>GJR-GARCH(1,1)</strong> model on the MLP training residuals:</p>



<div class="wp-block-math"><math display="block"><semantics><mrow><msubsup><mi>σ</mi><mi>t</mi><mn>2</mn></msubsup><mo>=</mo><mi>ω</mi><mo>+</mo><mi>α</mi><msubsup><mi>ϵ</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow><mn>2</mn></msubsup><mo>+</mo><mi>γ</mi><msub><mi>I</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow></msub><msubsup><mi>ϵ</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow><mn>2</mn></msubsup><mo>+</mo><mi>β</mi><msubsup><mi>σ</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow><mn>2</mn></msubsup></mrow><annotation encoding="application/x-tex">\sigma_t^2 = \omega + \alpha \epsilon_{t-1}^2 + \gamma I_{t-1} \epsilon_{t-1}^2 + \beta \sigma_{t-1}^2</annotation></semantics></math></div>



<ul class="wp-block-list">
<li><strong>Why GJR-GARCH over Standard GARCH?</strong>                                                                Crypto markets exhibit leverage effects: price drops often spark higher panic volatility than equivalent price rallies. The indicator term <math data-latex="I_{t-1}"><semantics><msub><mi>I</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow></msub><annotation encoding="application/x-tex">I_{t-1}</annotation></semantics></math> captures this asymmetry (<math data-latex="\gamma"><semantics><mi>γ</mi><annotation encoding="application/x-tex">\gamma</annotation></semantics></math>).</li>



<li><strong>Why <code>garchOrder = c(1, 1)</code>?</strong>                                                                                           The order <code>(1,1)</code> specifies a single lag for the ARCH component (<math data-latex="\epsilon_{t-1}^2"><semantics><msubsup><mi>ϵ</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow><mn>2</mn></msubsup><annotation encoding="application/x-tex">\epsilon_{t-1}^2</annotation></semantics></math>) and a single lag for the GARCH component (<math data-latex="\sigma_{t-1}^2"><semantics><msubsup><mi>σ</mi><mrow><mi>t</mi><mo>−</mo><mn>1</mn></mrow><mn>2</mn></msubsup><annotation encoding="application/x-tex">\sigma_{t-1}^2</annotation></semantics></math>). Empirical literature demonstrates that a single lag captures the vast majority of volatility persistence without overfitting.</li>



<li><strong>Why Skewed Student-t (<code>sstd</code>) Distribution?</strong>                                                           Bitcoin returns violate Gaussian normal distribution assumptions. They display <strong>fat tails</strong> (extreme events occur frequently) and <strong>skewness</strong> (asymmetric distribution). The <code>sstd</code> distribution explicitly models both heavy tails and skewness.</li>



<li><strong>Solver Selection</strong>:                                                                                                                     We use <code>solver = &quot;hybrid&quot;</code> in <code>rugarch</code> to combine optimization algorithms, ensuring convergence even under complex distribution parameters.</li>
</ul>



<div class="wp-block-math"><math display="block"><semantics><mrow><msub><mtext>Upper Bound</mtext><mi>t</mi></msub><mo>=</mo><msub><mover><mi>P</mi><mo stretchy="false">^</mo></mover><mi>t</mi></msub><mo>×</mo><mrow><mi>exp</mi><mo>⁡</mo></mrow><mo form="prefix" stretchy="false">(</mo><mo form="prefix" stretchy="false">+</mo><mn>1.96</mn><mo>×</mo><msub><mi>σ</mi><mi>t</mi></msub><mo form="postfix" stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\text{Upper Bound}_t = \hat{P}_t \times \exp(+1.96 \times \sigma_t)</annotation></semantics></math></div>



<div class="wp-block-math"><math display="block"><semantics><mrow><msub><mtext>Lower Bound</mtext><mi>t</mi></msub><mo>=</mo><msub><mover><mi>P</mi><mo stretchy="false">^</mo></mover><mi>t</mi></msub><mo>×</mo><mrow><mi>exp</mi><mo>⁡</mo></mrow><mo form="prefix" stretchy="false">(</mo><mo form="prefix" stretchy="false">−</mo><mn>1.96</mn><mo>×</mo><msub><mi>σ</mi><mi>t</mi></msub><mo form="postfix" stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\text{Lower Bound}_t = \hat{P}_t \times \exp(-1.96 \times \sigma_t)</annotation></semantics></math></div>



<p class="wp-block-paragraph">When the market is quiet, these confidence bounds contract automatically. During volatile regimes, the bounds expand, giving a realistic probabilistic envelope rather than static, uninformative confidence intervals.</p>



<h2 class="wp-block-heading">Complete R Implementation</h2>



<p class="wp-block-paragraph">Below is the complete, self-contained R script implementing the full pipeline using <code>tidymodels</code>, <code>brulee</code>, <code>rugarch</code>, and <code>plotly</code>.</p>


<pre>
# Integrated Forecasting Pipeline with tidymodels, yardstick, and plotly
library(tidymodels)
library(timetk)
library(brulee)
library(modeltime)
library(tidyquant)
library(plotly)
library(scales)
library(glue)
library(ggtext)
library(rugarch) # Added for GJR-GARCH volatility modeling

# 1. Fetch Data & Calculate Log Returns
df_raw &lt;- tq_get(&quot;BTC-USD&quot;) %&gt;%
  select(date, close) %&gt;%
  filter(date &gt;= last(date) - months(24)) %&gt;%
  drop_na() %&gt;%
  mutate(log_ret = log(close / lag(close))) %&gt;%
  drop_na()

# 2. Add Lag Features on Log Returns
df_prepared &lt;- df_raw %&gt;%
  tk_augment_lags(log_ret, .lags = 1:20) %&gt;%
  drop_na()

# 3. Time Series Split (10 Assessment Window)
split &lt;- time_series_split(
  df_prepared,
  assess = 10,
  cumulative = TRUE
)

df_train &lt;- training(split)
df_test  &lt;- testing(split)

# 4. Recipe Setup (Predict Log Return, Not Level Price)
recipe_mlp &lt;- recipe(log_ret ~ ., data = df_train) %&gt;%
  step_rm(date, close) %&gt;%
  step_dummy(all_nominal_predictors()) %&gt;%
  step_zv(all_predictors()) %&gt;%
  step_normalize(all_numeric_predictors())

# modeltime::naive_reg() requires the date column as a predictor to parse the time index
recipe_naive &lt;- recipe(log_ret ~ date, data = df_train)

# 5. Model Specifications
model_spec_mlp &lt;- mlp(
  hidden_units = c(32, 16),
  epochs = 100,
  dropout = 0.05,
  learn_rate = 0.005
) %&gt;%
  set_engine(&quot;brulee&quot;) %&gt;%
  set_mode(&quot;regression&quot;)

model_spec_naive &lt;- naive_reg() %&gt;%
  set_engine(&quot;naive&quot;)

# 6. Workflow Construction & Model Fitting
workflow_mlp &lt;- workflow() %&gt;%
  add_recipe(recipe_mlp) %&gt;%
  add_model(model_spec_mlp) %&gt;%
  fit(df_train)

workflow_naive &lt;- workflow() %&gt;%
  add_recipe(recipe_naive) %&gt;%
  add_model(model_spec_naive) %&gt;%
  fit(df_train)

# 7. Predictions & Price Reconstruction (Reference Code Logic)
preds_mlp_ret   &lt;- predict(workflow_mlp, df_test)
preds_naive_ret &lt;- predict(workflow_naive, df_test)

df_results &lt;- df_test %&gt;%
  mutate(
    yesterday_close = lag(close, default = first(close)),
    actual      = close,
    pred_brulee = yesterday_close * exp(preds_mlp_ret$.pred),
    pred_naive  = yesterday_close * exp(preds_naive_ret$.pred)
  )

# ==============================================================================
# 7.5. GJR-GARCH (Skewed Student-t) Dynamic Volatility & Confidence Bands
# ==============================================================================
# Calculate MLP residuals on the training set
train_preds_mlp &lt;- predict(workflow_mlp, df_train)$.pred
train_residuals &lt;- df_train$log_ret - train_preds_mlp

# GJR-GARCH(1,1) specification with Skewed Student-t distribution for fat-tails and asymmetry
garch_spec &lt;- ugarchspec(
  variance.model = list(model = &quot;gjrGARCH&quot;, garchOrder = c(1, 1)),
  mean.model = list(armaOrder = c(0, 0), include.mean = FALSE),
  distribution.model = &quot;sstd&quot;
)

# Fit GJR-GARCH model on training residuals
garch_fit &lt;- ugarchfit(spec = garch_spec, data = train_residuals, solver = &quot;hybrid&quot;)

# Forecast conditional volatility for the 10-day test horizon
garch_fore &lt;- ugarchforecast(garch_fit, n.ahead = 10)
dynamic_sigmas &lt;- sigma(garch_fore) # 10-day dynamic conditional standard deviations

# Attach dynamic standard deviations to test results and compute dynamic confidence bounds
df_results &lt;- df_results %&gt;%
  mutate(
    conditional_sigma = as.vector(dynamic_sigmas),
    # Dynamically scaled confidence bounds in price space (± 1.96 * conditional_sigma)
    conf_hi = pred_brulee * exp(1.96 * conditional_sigma),
    conf_lo = pred_brulee * exp(-1.96 * conditional_sigma)
  )

# 8. Accuracy Table Construction (Yardstick on Original Price Scale)
accuracy_table &lt;- tibble(
  .model_id   = c(1, 2),
  .model_desc = c(&quot;BRULEE MLP (32, 16) + GJR-GARCH&quot;, &quot;NAIVE&quot;),
  rmse        = c(rmse_vec(df_results$actual, df_results$pred_brulee),
                  rmse_vec(df_results$actual, df_results$pred_naive)),
  rsq         = c(rsq_vec(df_results$actual, df_results$pred_brulee),
                  rsq_vec(df_results$actual, df_results$pred_naive)),
  mape        = c(mape_vec(df_results$actual, df_results$pred_brulee),
                  mape_vec(df_results$actual, df_results$pred_naive)),
  mae         = c(mae_vec(df_results$actual, df_results$pred_brulee),
                  mae_vec(df_results$actual, df_results$pred_naive))
)

# Print Model Comparison Table
cat(&quot;=== MODEL ACCURACY EVALUATION TABLE (PRICE SCALE) ===\n&quot;)
cat(knitr::kable(accuracy_table, digits = 4, format = &quot;simple&quot;), sep = &quot;\n&quot;)

# 9. Modern Interactive Plotly Visualization (Reference Style)
brulee_mape &lt;- accuracy_table$mape[1]
df_eval &lt;- df_results

# Custom Hover Text Data Frames
df_plot_actual &lt;- df_eval %&gt;% select(date, actual)  %&gt;% mutate(text_actual = glue(&quot;&lt;b&gt;Actual Price:&lt;/b&gt; {scales::dollar(actual, accuracy = 0.01)}&lt;br&gt;&lt;b&gt;Date:&lt;/b&gt; {format(date, &#039;%b %d, %Y&#039;)}&quot;))
df_plot_pred   &lt;- df_eval %&gt;% select(date, pred_brulee) %&gt;% mutate(text_pred   = glue(&quot;&lt;b&gt;AI Prediction Price:&lt;/b&gt; {scales::dollar(pred_brulee, accuracy = 0.01)}&lt;br&gt;&lt;b&gt;Date:&lt;/b&gt; {format(date, &#039;%b %d, %Y&#039;)}&quot;))
df_plot_hi     &lt;- df_eval %&gt;% select(date, conf_hi) %&gt;% mutate(text_hi     = glue(&quot;&lt;b&gt;GJR-GARCH Upper Bound:&lt;/b&gt; {scales::dollar(conf_hi, accuracy = 0.01)}&lt;br&gt;&lt;b&gt;Date:&lt;/b&gt; {format(date, &#039;%b %d, %Y&#039;)}&quot;))
df_plot_lo     &lt;- df_eval %&gt;% select(date, conf_lo) %&gt;% mutate(text_lo     = glue(&quot;&lt;b&gt;GJR-GARCH Lower Bound:&lt;/b&gt; {scales::dollar(conf_lo, accuracy = 0.01)}&lt;br&gt;&lt;b&gt;Date:&lt;/b&gt; {format(date, &#039;%b %d, %Y&#039;)}&quot;))

# Build Base GGPlot Layer
p &lt;- ggplot() +
  geom_ribbon(data = df_eval, aes(x = date, ymin = conf_lo, ymax = conf_hi), fill = &quot;#808080&quot;, alpha = 0.18) +
  geom_point(data = df_plot_hi, aes(x = date, y = conf_hi, text = text_hi), color = &quot;transparent&quot;, alpha = 0, size = 3) +
  geom_point(data = df_plot_lo, aes(x = date, y = conf_lo, text = text_lo), color = &quot;transparent&quot;, alpha = 0, size = 3) +
  geom_line(data = df_plot_actual, aes(x = date, y = actual), color = &quot;#2c3e50&quot;, linewidth = 1.2) +
  geom_point(data = df_plot_actual, aes(x = date, y = actual, text = text_actual), color = &quot;#2c3e50&quot;, size = 2) +
  geom_line(data = df_plot_pred, aes(x = date, y = pred_brulee), color = &quot;#e74c3c&quot;, linetype = &quot;dashed&quot;, linewidth = 1.2) +
  geom_point(data = df_plot_pred, aes(x = date, y = pred_brulee, text = text_pred), color = &quot;#e74c3c&quot;, size = 2) +
  scale_y_continuous(labels = dollar_format(accuracy = 1)) +
  labs(
    x = &quot;&quot;, y = &quot;&quot;,
    title = paste0(
      &quot;&lt;span style=&#039;color:#2c3e50&#039;&gt;BTC-USD Prices&lt;/span&gt; vs &quot;,
      &quot;&lt;span style=&#039;color:#e74c3c&#039;&gt;MLP + GJR-GARCH Dynamic Forecast&lt;/span&gt;&lt;br&gt;&quot;,
      &quot;&lt;span style=&#039;font-size:12px; color:#555555;&#039;&gt;10-Day Horizon | Out-of-Sample MAPE: &quot;, round(brulee_mape, 2), &quot;&lt;/span&gt;&quot;
    )
  ) +
  theme_minimal() +
  theme(
    plot.title = element_markdown(hjust = 0.5, face = &quot;bold&quot;),
    plot.background = element_rect(fill = &quot;#ffffff&quot;, color = NA),
    panel.background = element_rect(fill = &quot;#ffffff&quot;, color = NA),
    panel.grid.minor = element_blank()
  )

# Typography Settings & Plotly Output
font_family &lt;- list(family = &quot;Roboto Slab, Sans-Serif&quot;, size = 16)
label_font  &lt;- list(font = list(family = &quot;Roboto Slab, Sans-Serif&quot;, size = 13))

ggplotly(p, tooltip = &quot;text&quot;) %&gt;%
  style(hoverlabel = label_font) %&gt;%
  layout(font = font_family)
</pre>


<figure data-wp-context="{"imageId":"6ad8a491b821c"}" data-wp-interactive="core/image" data-wp-key="6ad8a491b821c" class="wp-block-image size-large wp-lightbox-container"><img loading="lazy" data-attachment-id="12989" data-permalink="https://datageeek.com/2026/10/21/hybrid-machine-learning-financial-econometrics-building-volatility-aware-dynamic-forecast-bands-for-bitcoin/mlp_garch/" data-orig-file="https://datageeek.com/wp-content/uploads/2026/10/mlp_garch.png" data-orig-size="922,626" data-comments-opened="1" data-image-title="mlp_garch" data-image-description="" data-image-caption="" data-large-file="https://i1.wp.com/datageeek.com/wp-content/uploads/2026/10/mlp_garch.png?w=450&#038;ssl=1" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://i1.wp.com/datageeek.com/wp-content/uploads/2026/10/mlp_garch.png?w=450&#038;ssl=1" alt="" class="wp-image-12989" srcset_temp="https://datageeek.com/wp-content/uploads/2026/10/mlp_garch.png 922w, https://datageeek.com/wp-content/uploads/2026/10/mlp_garch.png?w=150 150w, https://datageeek.com/wp-content/uploads/2026/10/mlp_garch.png?w=300 300w, https://datageeek.com/wp-content/uploads/2026/10/mlp_garch.png?w=768 768w" sizes="(max-width: 922px) 100vw, 922px" data-recalc-dims="1" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Combining machine learning with traditional econometrics produces a hybrid system that overcomes the limitations of both fields:</p>



<ol class="wp-block-list">
<li><strong>Machine Learning</strong> captures non-linear relationships and trend movements.</li>



<li><strong>Econometrics (GARCH)</strong> supplies explicit statistical parameters for uncertainty and volatility clustering.</li>
</ol>



<p class="wp-block-paragraph">The resulting output provides realistic, dynamic risk envelopes that evolve with changing market regimes—giving quantitative traders and analysts a robust framework for volatility-aware decision-making.</p>



<p class="wp-block-paragraph"></p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://datageeek.com/2026/10/21/hybrid-machine-learning-financial-econometrics-building-volatility-aware-dynamic-forecast-bands-for-bitcoin/"> DataGeeek</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/hybrid-machine-learning-financial-econometrics-building-volatility-aware-dynamic-forecast-bands-for-bitcoin/">Hybrid Machine Learning & Financial Econometrics: Building Volatility-Aware Dynamic Forecast Bands for Bitcoin</a>]]></content:encoded>
					
		
		<enclosure url="https://datageeek.com/wp-content/uploads/2026/10/mlp_garch.png" length="0" type="" />
<enclosure url="https://1.gravatar.com/avatar/db5e3f9ef188ea98fe38ab05c5a3fad9fb52fe3472715a8fc02f7ea41731f77c?s=96&#038;d=identicon&#038;r=G" length="0" type="" />
<enclosure url="https://datageeek.com/wp-content/uploads/2026/10/image.png?w=981" length="0" type="" />
<enclosure url="https://datageeek.com/wp-content/uploads/2026/10/mlp_garch.png?w=922" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404358</post-id>	</item>
		<item>
		<title>Mark Padgham: Building the Machinery of Software Peer Review</title>
		<link>https://www.r-bloggers.com/2026/10/mark-padgham-building-the-machinery-of-software-peer-review/</link>
		
		<dc:creator><![CDATA[rOpenSci]]></dc:creator>
		<pubDate>Wed, 21 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://ropensci.org/blog/2026/10/21/mark-building-machinery-software-review/</guid>

					<description><![CDATA[<p>Mark Padgham is leaving rOpenSci at the end of October, and we want to take this moment to recognize his impact and say thank you for helping to shape rOpenSci.<br />
Mark joined rOpenSci as a staff member in December 2019 to help us expand software peer re...</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/mark-padgham-building-the-machinery-of-software-peer-review/">Mark Padgham: Building the Machinery of Software Peer Review</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://ropensci.org/blog/2026/10/21/mark-building-machinery-software-review/"> rOpenSci - open tools for open science</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p>Mark Padgham is leaving rOpenSci at the end of October, and we want to take this moment to recognize his impact and say thank you for helping to shape rOpenSci.</p>
<p><a href="http://ropensci.or/blog/2019/12/03/mark-padgham/" rel="nofollow" target="_blank">Mark joined rOpenSci as a staff member in December 2019</a> to help us expand software peer review to statistical topics.
However, Mark’s impact on rOpenSci has gone far beyond this one project.
Mark has not only shaped how rOpenSci reviews statistical software, but has also built much of the tooling that supports the peer review processes, and R packages which not only support but also strengthen our community.
If you have submitted a package to rOpenSci, reviewed or edited one, you have almost certainly used something Mark made!</p>
<h2>
Making a Mark
</h2><p>Mark’s most visible contribution is to statistical software peer review.
Reviewing a package that implements statistical methods is a different job from reviewing a package that accesses an API.
Mark tackled the task of translating what “good” means in a statistical package and how a reviewer can assess it.
He recruited a new set of editors with the expertise to tackel this work and turned insights into a concrete set of statistical software standards and the guide that accompanies them (<a href="https://ropensci.org/commcalls/feb2021-statsreview/" rel="nofollow" target="_blank">rOpenSci Statistical Software Testing and Peer Review Community Call</a>).
Mark also built the machinery to go with this new type review: <a href="https://docs.ropensci.org/pkgstats/" rel="nofollow" target="_blank">pkgstats</a> gathers information about a package’s structure, helps authors and editors see what a submission contains and how it is organised; <a href="https://docs.ropensci.org/srr/" rel="nofollow" target="_blank">srr</a> allows package authors to document their adherence to statistical software standards.</p>
<p>The result of all this work was <a href="https://ropensci.org/blog/2022/11/30/first-peer-reviewed-stats-packages/" rel="nofollow" target="_blank">our first peer-reviewed statistical packages</a>, published in November 2022, and to date we’ve had over 26 statistical packages go through this review!
Anyone who has gone through statistical software review has benefited from the groundwork Mark laid.</p>
<h2>
The engine of software review
</h2><p>Peer review at rOpenSci is a human process, but it runs on a lot of automation, and a good part of that automation is Mark’s work.</p>
<p>Mark built and maintained <a href="https://docs.ropensci.org/pkgcheck/" rel="nofollow" target="_blank">pkgcheck</a>, which runs automated checks on submitted packages, and the <a href="https://ropensci.org/blog/2022/02/01/pkgcheck-action/" rel="nofollow" target="_blank">GitHub Action</a> that allows anyone to run the same checks on their own repository.
pkgcheck uses <a href="https://docs.ropensci.org/goodpractice/" rel="nofollow" target="_blank">goodpractice</a>, another package maintained by Mark, that <a href="https://ropensci.org/blog/2026/06/01/goodpractice/" rel="nofollow" target="_blank">recently rolled out a host of updates and extension</a>.
Mark maintained <a href="https://ropensci.org/blog/2022/05/31/chatops-review-system-github/" rel="nofollow" target="_blank">Buffy</a>, the bot behind our chat review system on GitHub, which lets editors assign reviewers, track deadlines and move submissions along with simple comments, and many other pieces of infrastructure that make the whole system work.</p>
<h2>
Review is a human process
</h2><p>In early 2025, <a href="https://ropensci.org/blog/2025/02/27/news-february-2025/#mark-padgham-is-now-software-review-lead" rel="nofollow" target="_blank">Mark became our new Software Review Lead</a>.
In this role he was in charge of editorial management, curating the current editorial board and planning its future.
Mark helped guide peer-review through new challenges, such as how review should handle AI-assisted contributions, resulting in <a href="https://ropensci.org/blog/2026/02/26/ropensci-ai-policy/" rel="nofollow" target="_blank">rOpenSci’s first AI policy</a>; or how to deal with large software submissions, resulting in our first <em>group</em> peer review.</p>
<h2>
Tools for everyone
</h2><p>Mark’s care for rOpenSci goes beyond software.
He researched alternatives to GitHub, sharing the results in <a href="https://ropensci.org/blog/2025/12/17/beyond-github/" rel="nofollow" target="_blank">Beyond GitHub</a>, a thoughtful look at what it would mean for a community like ours to depend less on a single platform.</p>
<p>He has also served on our <a href="https://ropensci.org/code-of-conduct/" rel="nofollow" target="_blank">Code of Conduct committee</a>, work that helps ensure rOpenSci remains a welcoming and safe community.</p>
<p>Mark also builds tools that help the wider R community.
The <a href="https://ropensci.org/blog/2024/11/26/allcontributors/" rel="nofollow" target="_blank">allcontributors</a> package, inspired by the allcontributors project, makes it easy to recognise everyone who contributes to a project, not just those who write code.
His work on <a href="https://ropensci.org/blog/2025/09/11/repometrics-call-help/" rel="nofollow" target="_blank">repometrics</a> explores how to understand the health and activity of software repositories, and his work on <a href="https://ropensci.org/blog/2024/09/17/deposits-in-the-wild/" rel="nofollow" target="_blank">deposits</a> looks at how research data and software can be deposited in trusted repositories.</p>
<h2>
Thank you, Mark
</h2><p>Mark’s work has shaped how we review software, support contributors, and build a more reliable and sustainable open science ecosystem.
Thank you, Mark, for building so much of the infrastructure and process that we rely on, and for leaving it in better shape than you found it.</p>
<p>If you have benefited from Mark’s work, reach out to <a href="https://ropensci.org/author/mark-padgham" rel="nofollow" target="_blank">Mark</a> and let him know!
You can keep following his work through his website and social media.</p>
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://ropensci.org/blog/2026/10/21/mark-building-machinery-software-review/"> rOpenSci - open tools for open science</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/mark-padgham-building-the-machinery-of-software-peer-review/">Mark Padgham: Building the Machinery of Software Peer Review</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404356</post-id>	</item>
		<item>
		<title>TKCat meets LLMs: an MCP server for well-documented knowledge resources</title>
		<link>https://www.r-bloggers.com/2026/10/tkcat-meets-llms-an-mcp-server-for-well-documented-knowledge-resources/</link>
		
		<dc:creator><![CDATA[Patrice Godard]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 22:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://patzaw.github.io/posts/tkcat-mcp.html</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; ">
<p>1 Documentation was always the point<br />
TKCat (Tailored Knowledge Catalog) is built on a simple idea: a knowledge resource is more than a set of tables. In TKCat, each resource is a modeled database (MDB) that contains:</p>
<p>a general description of t...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/tkcat-meets-llms-an-mcp-server-for-well-documented-knowledge-resources/">TKCat meets LLMs: an MCP server for well-documented knowledge resources</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://patzaw.github.io/posts/tkcat-mcp.html"> Patrice Godard</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
 




<p><img src="https://i1.wp.com/patzaw.github.io/posts/images/tkcat-mcp.jpeg?w=450&#038;ssl=1" class="img-fluid" alt="Illustration of TKCat MCP generated by gemini"  data-recalc-dims="1"></p>
<section id="documentation-was-always-the-point" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="documentation-was-always-the-point"><span class="header-section-number">1</span> Documentation was always the point</h2>
<p><a href="https://patzaw.github.io/TKCat/" rel="nofollow" target="_blank">TKCat</a> (Tailored Knowledge Catalog) is built on a simple idea: a knowledge resource is more than a set of tables. In TKCat, each resource is a <em>modeled database</em> (MDB) that contains:</p>
<ul>
<li>a <strong>general description</strong> of the resource (source, version, scope),</li>
<li>a <strong>data model</strong> made with <a href="https://patzaw.github.io/ReDaMoR/" rel="nofollow" target="_blank">ReDaMoR</a>, with a comment on every table and field, plus primary keys, foreign keys and indexes,</li>
<li><strong>collection members</strong>, which say which fields refer to the same kind of concept (genes, diseases, tissues…) and make it possible to integrate different MDBs.</li>
</ul>
<p>This documentation was designed from the start to be machine-ready, and it turns out to be exactly what a large language model (LLM) needs in order to work with the data correctly.</p>
</section>
<section id="what-is-mcp" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="what-is-mcp"><span class="header-section-number">2</span> What is MCP</h2>
<p>The <a href="https://modelcontextprotocol.io/" rel="nofollow" target="_blank">Model Context Protocol (MCP)</a>, introduced by Anthropic, is an open standard that allows AI assistants to securely connect to external data sources, tools, and workflows. Think of it as a universal adapter for AI: instead of writing custom integrations for every combination of LLM and database, MCP provides a standardized client-server architecture.</p>
<p>In this setup, an MCP client (such as Claude Desktop, Claude Code, or Posit Assistant) communicates with an MCP server using a common protocol over local or remote connections. The server exposes a defined set of “tools” and resources that the assistant can discover and invoke dynamically based on the user’s natural language request.</p>
<p>For TKCat, MCP is a natural fit. Rather than forcing developers to build bespoke chat interfaces or write brittle custom prompts for every catalog, TKCat 1.3.0 exposes its modeled databases directly through a standard MCP server. Any MCP-compatible assistant instantly gains the structured vocabulary, relationship maps, and security guardrails it needs to navigate, query, and synthesize your knowledge resources safely.</p>
</section>
<section id="whats-new-in-tkcat-1.3.0" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="whats-new-in-tkcat-1.3.0"><span class="header-section-number">3</span> What’s new in TKCat 1.3.0</h2>
<p>TKCat 1.3.0 includes a read-only MCP server. With it, AI assistants such as Claude Code, Claude Desktop, Posit Assistant or any other MCP client can explore and query the MDBs of a chTKCat instance (TKCat on ClickHouse).</p>
<p>The server comes in two forms:</p>
<ul>
<li><code>chTKCat_mcp_server()</code>: a <strong>local</strong> server, started by the MCP client on the user’s computer (stdio transport).</li>
<li><code>chTKCat_mcp_http_server()</code>: a <strong>remote</strong> server that runs as a service next to the chTKCat instance (Streamable HTTP transport). Users don’t need R or TKCat on their computer.</li>
</ul>
<p>The assistant has a small set of tools that match how a careful analyst works with an unfamiliar resource:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 46%">
<col style="width: 53%">
</colgroup>
<thead>
<tr class="header">
<th>Step</th>
<th>Tools</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Find relevant resources</td>
<td><code>list_mdbs</code>, <code>search_tkcat</code></td>
</tr>
<tr class="even">
<td>Understand them</td>
<td><code>describe_mdb</code>, <code>describe_tables</code>, <code>list_collections</code></td>
</tr>
<tr class="odd">
<td>Look at the data</td>
<td><code>preview_table</code></td>
</tr>
<tr class="even">
<td>Answer the question</td>
<td><code>query_mdb</code> (ClickHouse SQL), <code>get_matrix_values</code></td>
</tr>
</tbody>
</table>
</section>
<section id="why-documented-databases-make-a-difference" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="why-documented-databases-make-a-difference"><span class="header-section-number">4</span> Why documented databases make a difference</h2>
<p>Ask an LLM a question about a database it has never seen and it will guess: it may infer table meanings from their names, assume joins and get the semantics of a column wrong. With TKCat, the assistant doesn’t need to guess:</p>
<ul>
<li><strong>It finds the right resource.</strong> MDB descriptions and keyword search over table and field comments point it to the resources that matter, even in a catalog of dozens of databases.</li>
<li><strong>It understands the data.</strong> Field comments explain codes, units and conventions. Foreign keys show how tables can be joined. So the SQL it writes follows the actual data model.</li>
<li><strong>It integrates resources.</strong> Collections say when, for example, a gene identifier in one MDB can be compared with a gene identifier in another one, and under which conditions (same source, same organism). The assistant can then answer questions that span several resources in a single query.</li>
<li><strong>Its answers can be checked.</strong> Every answer is built from explicit SQL queries on identified resources and versions. You can read them, rerun them in R and reuse them.</li>
</ul>
<p>A typical exchange looks like this:</p>
<blockquote class="blockquote">
<p><strong>User:</strong> Which drugs target genes associated with the phenotypes of this disease?</p>
<p><strong>Assistant:</strong> <em>searches the catalog → reads the descriptions of the phenotype, variant and drug MDBs → checks the collections that link them → previews a few records → runs a cross-MDB SQL query</em> → returns the answer, with the queries it ran and the resources it used.</p>
</blockquote>
<p>The better the documentation, the better the answers. The effort put into documenting a resource once now pays off every time someone asks a question about it, in plain language.</p>
</section>
<section id="built-for-shared-governed-environments" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="built-for-shared-governed-environments"><span class="header-section-number">5</span> Built for shared, governed environments</h2>
<p>Knowledge resources often have different access rules, and they are hosted by an organization rather than on a personal laptop. The MCP server was designed with this in mind:</p>
<ul>
<li><strong>Read-only by design</strong>: the assistant can query data but can’t write, create, delete or change grants.</li>
<li><strong>Per-user access</strong>: with the remote server, users log in with their own chTKCat credentials, which ClickHouse checks. Each user only sees the MDBs they are allowed to read.</li>
<li><strong>Controlled output</strong>: limits on returned rows, output size and query execution time keep tool calls small and fast.</li>
<li><strong>Scalable deployment</strong>: the remote server is stateless, so several instances can run behind a reverse proxy such as nginx, and tool calls are logged.</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://patzaw.github.io/TKCat/articles/mcp-architecture.svg" class="img-fluid figure-img"></p>
<figcaption>Remote MCP server architecture</figcaption>
</figure>
</div>
<p>As with any AI service, only expose MDBs whose content can be shared with the assistant under your organization’s policies.</p>
</section>
<section id="getting-started" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="getting-started"><span class="header-section-number">6</span> Getting started</h2>
<p>The best place to start is the new <strong><a href="https://patzaw.github.io/TKCat/articles/TKCat-EndToEnd.html" rel="nofollow" target="_blank">TKCat End-to-End vignette</a></strong>. It covers the whole process:</p>
<ol type="1">
<li>setting up a ClickHouse instance for TKCat,</li>
<li>preparing example MDBs (HPO, ClinVar, ChEMBL),</li>
<li>pushing them to ClickHouse and querying them from R,</li>
<li>deploying the MCP server (local and remote, with systemd and nginx),</li>
<li>configuring MCP clients (Claude Code, Claude Desktop, Posit Assistant).</li>
</ol>
<p>For reference details on the server, its tools and its parameters, see the <a href="https://patzaw.github.io/TKCat/articles/TKCat-MCP.html" rel="nofollow" target="_blank">MCP server vignette</a>.</p>
<p>For a complete introduction to TKCat, refer to the <a href="https://patzaw.github.io/TKCat/articles/TKCat.html" rel="nofollow" target="_blank">main vignette</a>.</p>
<p>Feedback and issues are welcome on <a href="https://github.com/patzaw/TKCat/issues" rel="nofollow" target="_blank">GitHub</a>.</p>


</section>

 
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://patzaw.github.io/posts/tkcat-mcp.html"> Patrice Godard</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/tkcat-meets-llms-an-mcp-server-for-well-documented-knowledge-resources/">TKCat meets LLMs: an MCP server for well-documented knowledge resources</a>]]></content:encoded>
					
		
		<enclosure url="https://patzaw.github.io/posts/images/tkcat-mcp.jpeg" length="0" type="image/jpeg" />

		<post-id xmlns="com-wordpress:feed-additions:1">404354</post-id>	</item>
		<item>
		<title>J’avais écrit les règles. Je n’avais pas encore vérifié leurs effets.</title>
		<link>https://www.r-bloggers.com/2026/10/javais-ecrit-les-regles-je-navais-pas-encore-verifie-leurs-effets/</link>
		
		<dc:creator><![CDATA[Boris Guarisma]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 16:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://blog.bguarisma.com/regles-ecrites-effets-verifies-agents-ia</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> REX d’une firme minimale : auditer sa veille IA, protéger ses notes et mesurer les limites du contrôle humain.</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/javais-ecrit-les-regles-je-navais-pas-encore-verifie-leurs-effets/">J’avais écrit les règles. Je n’avais pas encore vérifié leurs effets.</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://blog.bguarisma.com/regles-ecrites-effets-verifies-agents-ia"> Foundations</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
<p>Je dirige seul Qognito, une entreprise de conseil et de formation. Pour réaliser certaines activités de mon cœur de métier, je coordonne plusieurs équipes d’agents IA, <strong>mes « squads »</strong>. C’est ma manière de faire fonctionner une firme minimale : déléguer une partie du travail tout en restant responsable des orientations, des livrables et des engagements pris auprès de mes clients.</p>
<p><strong>La veille sur l’IA alimente ce travail.</strong> Une note peut m’aider à actualiser une formation, à examiner le besoin d’un prospect ou à préparer une proposition. Sa valeur tient à ce que je peux en faire ensuite.</p>
<p>Je conserve ces notes dans <strong>Obsidian</strong>, une application de gestion de connaissances. Des liens internes, les <em>wikilinks</em>, les relient entre elles : je peux retrouver une analyse et la rapprocher d’autres sujets. Ces liens facilitent l’exploration ; ils ne garantissent pas que plusieurs notes apportent des preuves indépendantes. Une erreur peut ainsi devenir un appui pour un raisonnement ultérieur.</p>
<p>J’avais organisé la production avec soin. Un agent <strong>prépare la source</strong>, un autre <strong>rédige l’analyse</strong>, un troisième <strong>évalue la note</strong>. Je décide de sa publication dans ma base. Voici le circuit attendu :</p>
<img src="https://i2.wp.com/cdn.hashnode.com/uploads/covers/61bc41016916c3593a6b04c3/f531f69d-8e91-4a86-a00e-cf9707daf210.png?w=578&#038;ssl=1" alt="Circuit attendu : préparation, rédaction, évaluation distincte puis décision humaine avant publication dans Obsidian." style="display:block;margin:0 auto" data-recalc-dims="1" />

<p><em>Ce schéma décrit les responsabilités prévues. Il reste à vérifier que les outils respectent ce circuit.</em></p>
<p>Dans ma formation <strong>SAGA-IA</strong>, je demande aux décideurs d’examiner ce qu’ils délèguent, les preuves disponibles et le coût du contrôle. <strong>J’ai soumis mon propre dispositif à ces exigences.</strong> L’audit a commencé par une découverte très simple.</p>
<h2>Les règles pouvaient changer avec celui qui les appliquait</h2>
<p>Les fichiers qui définissaient les consignes, les critères du juge et les contrôles étaient <strong>modifiables par les agents</strong>. Celui qui produisait une note avait donc techniquement la possibilité de changer les critères utilisés pour l’évaluer.</p>
<p>Je n’avais pas constaté qu’un agent l’avait fait. Mais la séparation des rôles reposait sur une consigne que les droits d’accès ne faisaient pas respecter.</p>
<p>J’ai fait protéger ces fichiers. Un agent peut proposer une modification ; sa mise en place exige une intervention humaine avec les droits d’administration. <strong>Dix tentatives de modification, par des voies différentes, ont ensuite été refusées.</strong> Les règles sont restées intactes.</p>
<p>Ces premières corrections précédaient la campagne consacrée aux protections, appelée « Phase A » dans mon audit. Elles avaient déplacé la question : les règles tenaient mieux, <strong>mais que pouvait-il encore arriver aux documents sur lesquels je m’appuyais ?</strong></p>
<h2>Publier une note ne suffisait pas à la préserver</h2>
<p>Au départ, une note rejoignait la base dès sa rédaction, avant le verdict du juge. J’avais ensuite instauré une étape d’attente, puis une autorisation humaine de publication. Le premier cycle réel avait suivi ce circuit : <strong>neuf sources traitées, neuf notes examinées et publiées par mes soins.</strong></p>
<p>Pourtant, une note déjà publiée pouvait encore être modifiée. Une source aussi. Le contrôle existant ne repérait pas ces changements. <strong>Une approbation donnée hier ne disait donc pas ce que je retrouverais demain.</strong></p>
<p>La Phase A a traité ce problème. Sur une copie de travail, trois altérations volontaires — une note modifiée, une note ajoutée hors circuit et une transcription modifiée — ont été détectées par <strong>un nouveau contrôle</strong> comparant les documents à une référence conservée. <strong>Des verrous</strong> ont également été posés sur les notes publiées et les sources préparées.</p>
<p>J’ai accepté une discipline pour moi aussi : plus de retouche directe dans Obsidian. Les changements doivent passer par le circuit prévu et laisser une trace. Sinon, comment distinguer ma correction d’une modification anormale ?</p>
<p>Préserver le contenu ne suffit toutefois pas à le rendre juste. Lors des premiers essais, le juge avait relevé « usually » traduit par « toujours », alors que les contrôles automatiques n’avaient rien signalé. <strong>Une nuance devenue certitude peut changer une décision.</strong></p>
<p>Le programme compare les documents ; le juge examine le sens ; je conserve la décision de publication. <strong>Cette répartition rend les responsabilités identifiables. Elle ne dispense pas de vérifier la qualité du jugement.</strong></p>
<h2>J’ai dû examiner mes propres examens</h2>
<p><strong>Un banc d’essai est une répétition sur des cas choisis, dont certains contiennent volontairement des pièges.</strong> J’en avais utilisé pour éprouver la veille. <strong>Deux constats</strong> m’ont obligé à revoir la confiance accordée à leurs résultats.</p>
<p>Le <strong>premier</strong> concernait le corrigé : les réponses attendues restaient accessibles aux agents. Imaginez un examen dont les candidats pourraient ouvrir les solutions. Cela fragilise l’évaluation, même sans preuve qu’ils les aient consultées. <strong>L’accès au corrigé a été protégé, puis son refus éprouvé sur le poste.</strong></p>
<p>Le <strong>second</strong> concernait les permissions. La copie utilisée pour certains essais était plus restrictive que l’environnement réel. Une action pouvait y demander un accord alors qu’elle passait librement au quotidien. Les résultats décrivaient bien les essais réalisés ; ils ne suffisaient pas à établir la même protection en usage réel.</p>
<p>La dernière étape de la Phase A devait donc réunir les protections dans une épreuve avec les permissions de production. Il fallait observer les tentatives, identifier ce qui les arrêtait et vérifier leurs effets sur les documents.</p>
<h2>Vingt tentatives, et une limite qui reste visible</h2>
<p>Le 19 octobre, <strong>vingt tentatives ont été exécutées et tracées</strong> dans les deux environnements d’agents utilisés. Les essais de modification de documents protégés et d’accès au corrigé ont rencontré des refus.</p>
<p>Deux créations de fichiers hors du circuit de publication ont toutefois réussi dans l’un des environnements. Le contrôle les a détectées ; elles ont ensuite été supprimées. <strong>La vérification finale a confirmé le retour de la base à son état initial.</strong></p>
<p><strong>Vingt tentatives documentées ne signifient donc pas vingt actions empêchées.</strong> Dans le fonctionnement courant, un ajout hors circuit peut rester présent jusqu’au contrôle déclenché par la prochaine publication. Cette limite reste ouverte.</p>
<p>La Phase A est close, avec une réserve précise : le prochain cycle réel doit encore montrer qu’une publication autorisée aboutit à une note effectivement verrouillée. Les refus constatés pendant les essais ne remplacent pas cette vérification de bout en bout.</p>
<p>L’évaluation de qualité menée en parallèle a donné sept cas réussis sur dix, avec une fausse alerte de fidélité et deux cas dont la portée restait à trancher. <strong>Protéger les documents n’établit pas que leur contenu s’est amélioré. Un examen rétrospectif des notes publiées reste prévu.</strong></p>
<h2>Le temps que le chronomètre ne voyait pas</h2>
<p>Pour comprendre le coût de cette maîtrise, il faut revenir au premier cycle réel. L’outil avait enregistré une à trois secondes par publication. J’avais pourtant consacré <strong>neuf minutes à relire les neuf notes avant de l’ouvrir.</strong> Ce temps a dû être déclaré séparément.</p>
<p>Ces neuf minutes suffisaient à ma décision de publier ; elles ne constituaient pas une comparaison complète de chaque note avec sa source. Une validation humaine doit préciser ce qui a réellement été vérifié et avec quel temps disponible.</p>
<p>La même exigence vaut pour les corrections. Pendant les premiers travaux, j’ai accepté un défaut de recoupement compté deux fois : une seule occurrence avait été constatée, et le script envisagé pour la traiter aurait été coûteux et lui-même faillible. <strong>Chaque anomalie ne justifie pas une règle supplémentaire.</strong></p>
<p>J’ai aussi fixé <strong>des critères d’arrêt, des responsables et des conditions de réexamen.</strong> Ces dispositions devront vivre dans l’usage. <strong>À ce stade, je n’ai démontré aucun gain global de rentabilité</strong> : la relecture, les reprises, la maintenance et l’audit font partie du coût à apprécier.</p>
<h2>Ce que je peux désormais justifier</h2>
<p>Le chemin parcouru tient en trois étapes :</p>
<img src="https://i1.wp.com/cdn.hashnode.com/uploads/covers/61bc41016916c3593a6b04c3/cbfd4c26-b900-40b4-ab65-381559eb0bfe.png?w=578&#038;ssl=1" alt="Progression : règles définies, protections éprouvées et limites identifiées, puis publication réelle et qualité encore à observer." style="display:block;margin:0 auto" data-recalc-dims="1" />

<p><em>Les protections testées apportent des preuves circonscrites. La dernière étape reste à observer.</em></p>
<p>Je peux mieux expliquer ce que je confie aux agents, ce qui résiste aux tentatives testées et ce qui demande encore un contrôle. <strong>Ma responsabilité de dirigeant consiste aussi à décider quelles incertitudes sont acceptables et lesquelles doivent limiter la délégation.</strong></p>
<p>Si vous coordonnez vous aussi une activité avec des agents, mon livre blanc gratuit <a href="https://qognito.io/livres-blancs/la-facture-fantome-ia/" rel="nofollow" target="_blank"><em>La facture fantôme de l’IA</em></a> propose une première lecture sur la supervision, les coûts complets et l’usage du temps gagné.</p>
<p>Pour travailler ces décisions, <a href="https://qognito.io/formations/saga-ia/volume-1/" rel="nofollow" target="_blank"><em>SAGA-IA — Volume 1</em></a> réunit le diagnostic de délégation et l’évaluation de la viabilité économique, avec des cas et des exercices. C’est le prolongement de ce REX : apprendre à justifier ce que l’on engage, les preuves que l’on demande et les conditions auxquelles on poursuivra.</p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://blog.bguarisma.com/regles-ecrites-effets-verifies-agents-ia"> Foundations</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/javais-ecrit-les-regles-je-navais-pas-encore-verifie-leurs-effets/">J’avais écrit les règles. Je n’avais pas encore vérifié leurs effets.</a>]]></content:encoded>
					
		
		<enclosure url="https://cdn.hashnode.com/uploads/covers/61bc41016916c3593a6b04c3/b4541293-3a7d-483d-a286-2f061c4ecf7c.jpg" length="0" type="image/jpeg" />

		<post-id xmlns="com-wordpress:feed-additions:1">404337</post-id>	</item>
		<item>
		<title>Get largest (e.g. mainland) polygons from a map</title>
		<link>https://www.r-bloggers.com/2026/10/get-largest-e-g-mainland-polygons-from-a-map/</link>
		
		<dc:creator><![CDATA[A.M. Barbosa]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 13:58:12 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">http://modtools.wordpress.com/?p=3571</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> I often need to get mainland from country / administrative region polygons, so I finally decided to write a function to help automate it. It selects the N largest feature(s) in a polygons or lines map. Here’s the code: The … Continue reading →</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/get-largest-e-g-mainland-polygons-from-a-map/">Get largest (e.g. mainland) polygons from a map</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://modtools.wordpress.com/2026/10/20/get-largest-e-g-mainland-polygons-from-a-map/"> modTools</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p class="wp-block-paragraph">I often need to get mainland from country / administrative region polygons, so I finally decided to write a function to help automate it. It selects the N largest feature(s) in a polygons or lines map. Here’s the code:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
selectLargest &lt;- function(x,  # terra SpatVector polygons or lines                          n = 1,  # number of largest features to retain                          dissolve = FALSE,  # whether to group contiguous features                          singleparts = TRUE  # whether to disaggregate multi-part features) {  stopifnot(inherits(x, &quot;SpatVector&quot;),            terra::geomtype(x) %in% c(&quot;polygons&quot;, &quot;lines&quot;))  if (dissolve) {    x &lt;- terra::aggregate(x)  }  if (singleparts) {    x &lt;- terra::disagg(x)  }  if (terra::is.polygons(x)) {    size_rank &lt;- order(terra::expanse(x), decreasing = TRUE)  } else if (terra::is.lines(x)) {    size_rank &lt;- order(terra::perim(x), decreasing = TRUE)  }  x[size_rank[1:n], ]}</pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">The dissolving of internal borders is optional. Here are some usage examples:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
library(terra)library(geodata)# import a USA map and get the conterminous and continental parts:usa &lt;- geodata::gadm(&quot;United States&quot;, level = 1, path = tempdir())mainland_usa &lt;- selectLargest(usa, dissolve = TRUE)contin_usa &lt;- selectLargest(usa, dissolve = TRUE, n = 2)  # two largest polygons, to include Alaskaplot(usa, main = &quot;USA&quot;)plot(mainland_usa, main = &quot;Conterminous USA&quot;)plot(contin_usa, main = &quot;Continental USA&quot;)# import a UK map and get Great Britain:uk &lt;- geodata::gadm(&quot;United Kingdom&quot;, path = tempdir())great_britain &lt;- selectLargest(uk, dissolve = TRUE)plot(uk, main = &quot;UK&quot;)plot(great_britain, main = &quot;Great Britain&quot;)# import a Portugal + Spain map and get the Iberian Peninsula:ptes &lt;- geodata::gadm(c(&quot;Portugal&quot;, &quot;Spain&quot;), path = tempdir())iberia &lt;- selectLargest(ptes, dissolve = TRUE)plot(ptes, main = &quot;Portugal and Spain&quot;)plot(iberia, main = &quot;Iberian Peninsula&quot;)</pre>
		</div>
	</div>
</div>


<figure class="wp-block-image size-large"><a href="https://i0.wp.com/modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?ssl=1" rel="nofollow" target="_blank"><img loading="lazy" data-attachment-id="3578" data-permalink="https://modtools.wordpress.com/2026/10/20/get-largest-e-g-mainland-polygons-from-a-map/mainlands-2/" data-orig-file="https://modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png" data-orig-size="1353,1614" data-comments-opened="1" data-image-title="mainlands" data-image-description="" data-image-caption="" data-large-file="https://modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?w=584" src="https://i0.wp.com/modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?w=450&#038;ssl=1" alt="" class="wp-image-3578" srcset_temp="https://i0.wp.com/modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?w=450&#038;ssl=1 858w, https://modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?w=126 126w, https://modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?w=251 251w, https://modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?w=768 768w, https://modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png 1353w" sizes="(max-width: 858px) 100vw, 858px" data-recalc-dims="1" /></a></figure>



<p class="wp-block-paragraph">As usual, feedback is welcome!</p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://modtools.wordpress.com/2026/10/20/get-largest-e-g-mainland-polygons-from-a-map/"> modTools</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/get-largest-e-g-mainland-polygons-from-a-map/">Get largest (e.g. mainland) polygons from a map</a>]]></content:encoded>
					
		
		<enclosure url="https://2.gravatar.com/avatar/e1a349777384e70d2eb7ee3f26788864b1c880ec271cfc1cb8f9d8cc3000cd11?s=96&#038;d=retro&#038;r=G" length="0" type="" />
<enclosure url="https://modtools.wordpress.com/wp-content/uploads/2026/10/mainlands-1.png?w=858" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404339</post-id>	</item>
		<item>
		<title>Getting the pipe shortcut to work in Positron</title>
		<link>https://www.r-bloggers.com/2026/10/getting-the-pipe-shortcut-to-work-in-positron/</link>
		
		<dc:creator><![CDATA[Giles]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 06:34:12 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://www.gilesd-j.com/?p=4785</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> For the life of me, I couldn't get Ctrl+Shift+M to work for inserting pipes (&#124;__) when editing a Quarto document in Positron visual mode. After countless hours fiddling with keybindings.json, I developed a functional, but obtuse workaround for Windows. It's not pretty, but it works. This post outlines ...</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/getting-the-pipe-shortcut-to-work-in-positron/">Getting the pipe shortcut to work in Positron</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://www.gilesd-j.com/2026/10/20/getting-the-pipe-shortcut-to-work-in-positron/"> Data Analytics and AI Archives - Giles</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p class="wp-block-paragraph"><em><strong>TLDR:</strong> For the life of me, I couldn’t get <code>Ctrl+Shift+M</code> to work for inserting pipes (<code>|&gt;</code>) when editing a Quarto document in Positron visual mode. After countless hours fiddling with <code>keybindings.json</code>, I developed a functional, but obtuse workaround for Windows. It’s not pretty, but it works. This post outlines the approach in case it helps others facing the same problem. </em></p>



<p class="wp-block-paragraph"><strong>Note: </strong>this problem is already on the radar of the development team, so will no doubt be fixed soon. I’ve also only outlined a workaround for Windows. Feel free to <a href="https://www.gilesd-j.com/contact/" rel="nofollow" target="_blank">reach out</a> if you’ve found a better solution (or a solution that works on other platforms) and I’ll add this to the post.</p>



<h2 class="wp-block-heading">The problem</h2>



<p class="wp-block-paragraph">I’m a big fan of Quarto as it provides a seamless way to mix data, code and narrative text without ever leaving my IDE. I’ve also been trying to move from RStudio to Positron as I find it snappier, works better with AI tools <em>and </em>works well with Python. Unfortunately, Positron is still experiencing growing pains as a newish IDE, including some shortcuts not working when editing Quarto documents in visual mode. </p>



<p class="wp-block-paragraph">This isn’t a new issue (e.g: <a href="https://github.com/orgs/quarto-dev/discussions/11991" rel="nofollow" target="_blank">1</a>, <a href="https://github.com/posit-dev/positron/issues/1955" rel="nofollow" target="_blank">2</a> and <a href="https://github.com/posit-dev/positron/issues/1805" rel="nofollow" target="_blank">3</a>), so is sure to be fixed in the not-too-distant future (likely just after I post this). But, after countless hours messing around with keybindings.json and hassling people from Posit (apologies, Isabella) I asked Claude if it had a solution. </p>



<p class="wp-block-paragraph">It did.</p>



<p class="wp-block-paragraph">And it works.</p>



<p class="wp-block-paragraph">So, I thought I’d share it here, despite the fact it <em>doesn’t spark joy</em> with the minimalist inside of me:</p>



<h2 class="wp-block-heading">Duct tape and thumbtacks: intercept the shortcut before it reaches Positron</h2>



<p class="wp-block-paragraph">As far as I can tell, the problem comes from Positron / VSCode intercepting some shortcuts when Quarto is in visual mode, which is probably why adjusting the settings did nothing for me. As a (temporary) solution to this, Claude suggested installing a program called <a href="https://www.autohotkey.com/" rel="nofollow" target="_blank">AutoHotkey v2</a> and creating a script operates when Positron is active. </p>



<p class="wp-block-paragraph">Obviously, as an independent-minded person I did what I’m told and here’s how you can do the same:</p>



<h3 class="wp-block-heading">1. Install AutoHotkey v2</h3>



<p class="wp-block-paragraph">Get Version 2 from <a href="https://www.autohotkey.com/" rel="nofollow" target="_blank">autohotkey.com</a> (the code below will not run under v1).</p>



<h3 class="wp-block-heading">2. Create the script</h3>



<p class="wp-block-paragraph"> </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" loading="lazy" src="https://i2.wp.com/www.gilesd-j.com/wp-content/uploads/2026/10/image-2.png?w=450&#038;ssl=1" alt="" class="wp-image-4793" srcset_temp="https://i2.wp.com/www.gilesd-j.com/wp-content/uploads/2026/10/image-2.png?w=450&#038;ssl=1 631w, https://www.gilesd-j.com/wp-content/uploads/2026/10/image-2-300x132.png 300w" sizes="auto, (max-width: 631px) 100vw, 631px" data-recalc-dims="1" /></figure>



<p class="wp-block-paragraph"> </p>



<p class="wp-block-paragraph">Open Autohotkey2 (or AutoHotKey Dash) and select <strong>New</strong> <strong>Script</strong> to create a new blank .ahk script in a secluded folder of your choice. Give it a name like ‘positron-keys.ahk’ so future <em>you </em>knows what it’s doing there and use a text editor to replace the contents of the file with the code below (or the equivalent depending on your shortcut preferences):</p>



<p class="wp-block-paragraph"><em>(SendText sends the literal characters to the Quarto document, so nothing gets re-interpreted by Positron, while #HotIf restricts the behavior to Positron, so it doesn’t interfere with other applications.)</em></p>



<pre>#Requires AutoHotkey v2.0
#SingleInstance Force

#HotIf WinActive(&quot;ahk_exe Positron.exe&quot;)

^+m::SendText(&quot; |&gt; &quot;)    ;Ctrl+Shift+M -&gt; native pipe
!-::SendText(&quot; &lt;- &quot;)     ;Alt+-        -&gt; assignment arrow

#HotIf</pre>



<h3 class="wp-block-heading">3. Run it</h3>



<p class="wp-block-paragraph">To run the code and launch AutoHotkey, open the ‘positron-keys.ahk’ file. If it worked, a green <strong>H</strong> should appear in the system tray. </p>



<h3 class="wp-block-heading">4. Test it</h3>



<p class="wp-block-paragraph">To test it you can now open Positron and try using Ctrl + Shift + M to insert a pipe in a Quarto when in visual mode. Hopefully it works, as this is the only idea covered in this post. </p>



<h3 class="wp-block-heading">5. Make it persistent</h3>



<p class="wp-block-paragraph">To avoid needing to load the code each time you want to use Positron, you can then add a shortcut to the script that loads at startup. For Windows, you can open the startup folder by typing <code>Win+R</code> and executing the command ‘<code>shell:startup</code>‘. Right-drag <code>positron-keys.ahk</code> into the folder and choose <strong>Create shortcut here</strong>. </p>



<p class="wp-block-paragraph"><strong>Note: </strong>if you have other solutions, please <a href="https://www.gilesd-j.com/contact/" rel="nofollow" target="_blank">let me know</a>. I’ll happily update this post if others find it useful. </p>



<p class="wp-block-paragraph"><strong>AI use:</strong> Claude suggested this solution and wrote the first draft of this post, which I heavily edited. </p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://www.gilesd-j.com/2026/10/20/getting-the-pipe-shortcut-to-work-in-positron/" rel="nofollow" target="_blank">Getting the pipe shortcut to work in Positron</a> appeared first on <a href="https://www.gilesd-j.com/" rel="nofollow" target="_blank">Giles</a>.</p>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://www.gilesd-j.com/2026/10/20/getting-the-pipe-shortcut-to-work-in-positron/"> Data Analytics and AI Archives - Giles</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/getting-the-pipe-shortcut-to-work-in-positron/">Getting the pipe shortcut to work in Positron</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404333</post-id>	</item>
		<item>
		<title>Celebrating 15 Years of rOpenSci: Then, Now &#038; Next</title>
		<link>https://www.r-bloggers.com/2026/10/celebrating-15-years-of-ropensci-then-now-next/</link>
		
		<dc:creator><![CDATA[rOpenSci]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://ropensci.org/blog/2026/10/20/15yo-events/</guid>

					<description><![CDATA[<p>In June 2026, rOpenSci marked its 15th anniversary with three community celebration events: a themed coworking event and two extra events. This post captures the reflections and ideas shared by participants across the two special gatherings named &#038;ldq...</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/celebrating-15-years-of-ropensci-then-now-next/">Celebrating 15 Years of rOpenSci: Then, Now & Next</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://ropensci.org/blog/2026/10/20/15yo-events/"> rOpenSci - open tools for open science</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p>In June 2026, rOpenSci marked its 15th anniversary with three community celebration events: a themed coworking event and two extra events. This post captures the reflections and ideas shared by participants across the two special gatherings named <strong>“rOpenSci Community Celebration: Then, Now &#038; Next”</strong> that were held to accommodate different time zones.</p>
<h2>
How the events worked
</h2><p>Our goal was to create a space for people to have conversations, get to know each other and also reflect on rOpenSci’s past, present and future.</p>
<p>After a welcome and a brief introduction, participants joined three successive breakout rounds of 15 minutes each, one for each theme: the <strong>past</strong>, the <strong>present</strong>, and the <strong>future</strong>. Between rounds, attendees took part in a <strong>Scavenger Hunt</strong> (a regular feature of our <a href="https://ropensci.org/blog/2023/06/21/coworking/" rel="nofollow" target="_blank">co-working sessions</a>), finding something in their physical surroundings that matched a given prompt.</p>
<p>The Scavenger Hunt is a fun moment together, and we also used it as a natural prompt for introductions at the start of the next breakout room — “what did you find?” becomes a nice and easy opening for conversations.</p>
<p>As in our community calls, workshops and co-working sessions, a shared document served as the place where everyone could take notes in real time, add thoughts, and keep contributing for a couple of days after the event ended. <a href="https://ropensci.org/author/noam-ross/" rel="nofollow" target="_blank">Noam Ross</a> named this <em>“process is product”</em> approach, caring about <em>how</em> things happen, not just <em>what</em> happens, as one of rOpenSci’s defining traits.</p>
<div class="row">
<div class="col-md-6">
<figure><img src="https://i1.wp.com/ropensci.org/blog/2026/10/20/15yo-events/15yoevents.jpg?w=578&#038;ssl=1"
alt="Zoom meeting with 15 years old welcoming slide and attendees to the event" data-recalc-dims="1"><figcaption>
<p>First event celebrating 15 years of rOpenSci</p>
</figcaption>
</figure>
</div>
<div class="col-md-6">
<figure><img src="https://i2.wp.com/ropensci.org/blog/2026/10/20/15yo-events/15yosecond_event.jpg?w=578&#038;ssl=1"
alt="Second Zoom meeting with 15 years old welcoming slide and attendees to the event" data-recalc-dims="1"><figcaption>
<p>Second meeting to celebrate 15 years of rOpenSci</p>
</figcaption>
</figure>
</div>
</div>
<h2>
Round 1 &#8211; The Past: What made rOpenSci special?
</h2><p>The first breakout asked participants how they first connected with rOpenSci and what their favourite memory was.</p>
<p>Several people traced their connection back to a talk about rOpenSci or to a personal introduction from a community member who made them feel welcome from the start.</p>
<p>The <a href="https://ropensci.org/tags/unconf/" rel="nofollow" target="_blank"><strong>unconferences</strong></a> were remembered with particular warmth and excitement: the chance to sit next to people, see how they work, talk about R, and be part of something as it was taking shape. <em>“Meeting my heroes”</em>, as one participant put it.</p>
<p>Other things people named as favourite:</p>
<ul>
<li><a href="https://ropensci.org/software-review/" rel="nofollow" target="_blank"><strong>The peer-review process</strong></a>: praised by editors, reviewers and authors alike, both for the quality of the process and for the connections it created.</li>
</ul>
<blockquote>
<p>Being invited to write a blog post after a first package review … made me feel very cool and a valued part of the community.&#8221; &#8211; <a href="https://ropensci.org/author/alex-koiter/" rel="nofollow" target="_blank">Alex Koiter</a></p>
</blockquote>
<ul>
<li>
<p><a href="https://devguide.ropensci.org/" rel="nofollow" target="_blank"><strong>The dev guide</strong></a>: a detailed and evolving documentation with knowledge that isn’t easily found elsewhere.</p>
</li>
<li>
<p><a href="https://ropensci.org/champions/" rel="nofollow" target="_blank"><strong>The Champions Program</strong></a> and <a href="https://ropensci.org/blog/2026/06/02/ftc-guide/" rel="nofollow" target="_blank"><strong>the mini-hackathons</strong></a>, spaces that provide friendly and guided entry points for open source contributors and R package developers.</p>
</li>
<li>
<p><a href="https://ropensci.org/coworking" rel="nofollow" target="_blank"><strong>Co-working sessions</strong></a> are a place were you can find “your people,” a place where you don’t need to explain what R is.</p>
</li>
</ul>
<blockquote>
<p>It’s a satisfying community ‘distraction/procrastination’ from our normal jobs, that keeps us from being lonely and brings us together from different worlds to work on something that’s benefitting people.&#8221; &#8211; <a href="https://ropensci.org/author/ronny-hernandez-mora/" rel="nofollow" target="_blank">Ronny Hernandez Mora</a></p>
</blockquote>
<h2>
Round 2 &#8211; The Present: What does rOpenSci do well?
</h2><p>The second round invited reflection on what rOpenSci does well today and what participants want to get more involved in.</p>
<p>The most common answer was <strong>creating a welcoming space</strong>.
Participants then commented on the different places where they experience the friendly and welcoming community:</p>
<ul>
<li>
<p><a href="https://ropensci.org/coworking" rel="nofollow" target="_blank"><strong>Co-working</strong></a> came up again here, noted as something other communities have adopted from rOpenSci.</p>
</li>
<li>
<p>The <strong>Slack workspace</strong> and the atmosphere it creates: a common space for learning, getting advice, sharing ideas, and feeling part of something larger.</p>
</li>
<li>
<p><strong>Good documentation</strong> for package submission, which lowers the barrier to participating.</p>
</li>
<li>
<p>Some participants expressed interest in getting more involved in the <strong>Champions Program</strong>, along with honest questions about whether the entry points are accessible enough.</p>
</li>
<li>
<p>The sense that rOpenSci’s quality in different activities comes from its community participation.</p>
</li>
</ul>
<blockquote>
<p>You can jump into a role and the community will support you. If there is anything you want to do, start doing it and you will get the support you need.&#8221; &#8211; <a href="https://ropensci.org/author/adam-sparks/" rel="nofollow" target="_blank">Adam Sparks</a></p>
</blockquote>
<h2>
Round 3 &#8211; The Future: What comes next?
</h2><p>The third round asked what participants would like to see rOpenSci do next, and what ideas from other communities could be worth adopting or partnering on.</p>
<p><strong>In-person and hybrid gatherings</strong> were a recurring theme: local meetups, local chapters, satellite events at larger R conferences like <a href="https://www.r-project.org/conferences/" rel="nofollow" target="_blank">useR!</a>, and the <em>return of unconferences</em>, potentially co-organised with partner communities.</p>
<p>Other ideas from across the rooms:</p>
<ul>
<li>
<p><strong>First-time contributor hackathons</strong>, possibly with a GenAI focus</p>
</li>
<li>
<p>A <strong>24-hour unconference</strong> modeled on formats used by other communities like <a href="https://sortee.org/" rel="nofollow" target="_blank">SORTEE</a></p>
</li>
<li>
<p><strong>Career panels</strong> showing the range of roles in research software engineering and data-centric work</p>
</li>
<li>
<p>An rOpenSci-led package for R package development, like <a href="https://usethis.r-lib.org/" rel="nofollow" target="_blank">usethis</a>, that supports GitLab, Codeberg, and other platforms beyond GitHub</p>
</li>
<li>
<p>Deeper <strong>coalitions with open science organizations</strong></p>
</li>
<li>
<p>Connecting <strong>package authors with graphic designers</strong> to help them with their image and visual identity</p>
</li>
<li>
<p>Exploring <strong><a href="https://zarr.dev/" rel="nofollow" target="_blank">Zarr</a> in R</strong> as an area where rOpenSci could help shape the ecosystem</p>
</li>
<li>
<p>Better serving <strong>very beginners</strong>, the people who are still asking “what is R, anyway?”</p>
</li>
<li>
<p><strong>Merchandise and swag</strong>, we all want our rOpenSci hex sticker <img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</li>
</ul>
<p>One thread in the conversation pointed to a real tension: rOpenSci has spent 15 years building carefully and steadily. GenAI is moving much faster. How do you maintain that care and those foundations when the pace of change accelerates? Even when we didn’t have any easy answers, this question is the right one to be discussing with the community.</p>
<h2>
Closing thoughts
</h2><p>Reading through the notes from both events, a few things are consistent.</p>
<p>People talk about rOpenSci not just as a set of tools or infrastructure, but as a place where they found colleagues, had their work taken seriously, and felt like valued contributors.</p>
<p>The <a href="https://ropensci.org/software-review/" rel="nofollow" target="_blank">software peer-review process</a>, the <a href="https://devguide.ropensci.org/" rel="nofollow" target="_blank">dev guide</a>, the <a href="https://ropensci.org/coworking/" rel="nofollow" target="_blank">co-working sessions</a>, the <a href="https://ropensci.org/champions/" rel="nofollow" target="_blank">Champions Program</a>, these work because the community behind them is invested in supporting them and find them useful.</p>
<p>At 15 years, the ideas that surfaced – local chapters, hackathons, coalitions, better onboarding for newcomers – suggest a community that still wants to try new things.</p>
<blockquote>
<p>It works because everyone is welcome.&#8221; &#8211; several attendees at the events <img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f638.png" alt="😸" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</blockquote>
<p><em>Thank you to everyone who joined and shared their thoughts. <img src="https://s.w.org/images/core/emoji/13.0.0/72x72/1f499.png" alt="💙" class="wp-smiley" style="height: 1em; max-height: 1em;" /></em></p>
<p><em>These events were part of rOpenSci’s 15th anniversary celebration series. Keep an eye on <a href="https://ropensci.org/events/" rel="nofollow" target="_blank">rOpenSci’s events calendar</a>, our <a href="https://ropensci.org/tags/anniversary/" rel="nofollow" target="_blank">blog</a> and our <a href="https://ropensci.org/news/" rel="nofollow" target="_blank">newsletter</a> for more content around our anniversary.</em></p>
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://ropensci.org/blog/2026/10/20/15yo-events/"> rOpenSci - open tools for open science</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/celebrating-15-years-of-ropensci-then-now-next/">Celebrating 15 Years of rOpenSci: Then, Now & Next</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404335</post-id>	</item>
		<item>
		<title>From a raw spreadsheet to a published data package in a day</title>
		<link>https://www.r-bloggers.com/2026/10/from-a-raw-spreadsheet-to-a-published-data-package-in-a-day/</link>
		
		<dc:creator><![CDATA[Lars Schöbitz]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://openwashdata.org/pages/blog/posts/2026-10-20-solidwastekampala-story/</guid>

					<description><![CDATA[<p>On 22 July 2026 at 15:05, I made the first commit to a new repository. It held an empty R package and one Excel workbook with field data from a household waste study in Kampala, Uganda. Three minutes later I sent the first prompt to Claude Code:...</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/from-a-raw-spreadsheet-to-a-published-data-package-in-a-day/">From a raw spreadsheet to a published data package in a day</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://openwashdata.org/pages/blog/posts/2026-10-20-solidwastekampala-story/"> openwashdata</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
 





<p>On 22 July 2026 at 15:05, I made the first commit to a new repository. It held an empty R package and one Excel workbook with field data from a household waste study in Kampala, Uganda. Three minutes later I sent the first prompt to Claude Code:</p>
<blockquote class="blockquote">
<p>I am building a new data package and received a dataset in <span class="citation" data-cites="data-raw">@data-raw</span>/ to process for clean up. […] strictly start with cleaning the data, writing a dictionary. […] make a plan first.</p>
</blockquote>
<p>On 24 July at 00:18, the repository held version 1.0.0 of <a href="https://github.com/openwashdata/solidwastekampala" rel="nofollow" target="_blank">solidwastekampala</a> with a Zenodo DOI: a tidy and validated dataset of 103 households, a data dictionary for 37 variables, documentation, a website, machine-readable metadata, a citation with the ORCID iDs of the authors, and automated checks on five platforms.</p>
<p>My own time on it was no more than about three hours, spread over four sessions. The repository records every step in its commits, issues and pull requests, and in an archive of every prompt I sent.</p>
<section id="the-data" class="level2">
<h2 class="anchored" data-anchor-id="the-data">The data</h2>
<p>The dataset belongs to the paper <a href="https://doi.org/10.3389/fenvs.2026.1889921" rel="nofollow" target="_blank">Quantity and composition of domestic solid waste in Kampala City as influenced by socioeconomic factors</a> by Katukiza et al. at Makerere University, published in Frontiers in Environmental Science in September 2026. Field teams weighed and sorted the waste of 103 households over seven days in three parishes that stand for three income levels: Bwaise I (low), Bukoto I (middle) and Ggaba (high).</p>
<p>The data arrived the way field data usually arrives. The workbook had sheets with two rows of merged headers, summary rows at the bottom, derived results next to the raw data, and the typos that manual data entry leaves behind.</p>
</section>
<section id="session-1-a-plan-before-any-code" class="level2">
<h2 class="anchored" data-anchor-id="session-1-a-plan-before-any-code">Session 1: a plan before any code</h2>
<p>The first session produced no cleaning code, only a plan based on a close look at every sheet.</p>
<ul>
<li>Only three sheets hold raw data. All other sheets hold derived results.</li>
<li>One number is stored as text. A cell for metals in Ggaba reads <code>2..674</code>, and the SUM formula in Excel skipped it without a warning. The recorded totals stay as they are, because the values per day, per person and the density are calculated from them.</li>
<li>The means per parish reproduce the headline figures of the paper (0.43, 0.53 and 0.98 kg of waste per person per day), which confirmed that these were the right sheets.</li>
</ul>
<p>The plan also set the rule for the rest of the project. We preserve the data as recorded. Seven inconsistencies inside the workbook are documented and tested for, and none was corrected without a record.</p>
</section>
<section id="session-2-from-plan-to-package" class="level2">
<h2 class="anchored" data-anchor-id="session-2-from-plan-to-package">Session 2: from plan to package</h2>
<p>Early on Wednesday the plan became GitHub issues, one per step and each small enough to review on its own. I asked for one issue at a time, with a stop for my review before each commit.</p>
<p>By 06:55 the processing script was in place. It reads all sheets as text so that nothing is lost on import, gives the 37 columns consistent names, fixes the <code>2..674</code> cell and repairs two malformed household IDs. A map per column harmonises the categories, for example <code>IIIiterate</code> to <code>Illiterate</code> and <code>Unpaced</code> to <code>Unpaved</code>. A validation block checks the result, including the inconsistencies we know about: the sums of the waste categories match the recorded totals in 99 of 103 rows, and the script names the four exceptions.</p>
<p>At 07:19 I merged the first pull request with the clean data, CSV and XLSX exports and the dictionary. At 08:03 the second followed with what a stranger needs to use the data: package metadata, documentation, a README that reproduces a figure of the paper, a website, and metadata in schema.org format.</p>
</section>
<section id="session-3-the-review" class="level2">
<h2 class="anchored" data-anchor-id="session-3-the-review">Session 3: the review</h2>
<p>Every openwashdata package goes through a review against a versioned standard, here version 1.0.0. The review consists of four checklist issues, and each one closes with its own pull request.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Step</th>
<th>Issue</th>
<th>Pull request</th>
<th>Merged</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Metadata and citation</td>
<td>#14</td>
<td>#16</td>
<td>12:22</td>
</tr>
<tr class="even">
<td>Data content and processing</td>
<td>#17</td>
<td>#18</td>
<td>12:58</td>
</tr>
<tr class="odd">
<td>Documentation</td>
<td>#19</td>
<td>#20</td>
<td>13:52</td>
</tr>
<tr class="even">
<td>Tests and automated checks</td>
<td>#21</td>
<td>#22</td>
<td>14:50</td>
</tr>
</tbody>
</table>
<p>The metadata step found five ORCID iDs of the co-authors in the public registry. Two matches were uncertain, so it opened an issue asking the main author to confirm them. The data step reran the processing script and got the committed data back, byte for byte. The last step added checks on five platforms, with 0 errors, 0 warnings and 0 notes.</p>
</section>
<section id="session-4-the-authors-reply" class="level2">
<h2 class="anchored" data-anchor-id="session-4-the-authors-reply">Session 4: the authors reply</h2>
<p>At 23:55 the prompt archive records my message “ORCIDs have arrived”. The main author had confirmed the uncertain matches, sent a missing ORCID iD and settled the spelling of a name. The corrections went into all metadata files. At 00:15 I released version 1.0.0, and at 00:18 Zenodo issued the DOI.</p>
</section>
<section id="how-the-package-meets-the-fair-principles" class="level2">
<h2 class="anchored" data-anchor-id="how-the-package-meets-the-fair-principles">How the package meets the FAIR principles</h2>
<ul>
<li><strong>Findable</strong> through a DOI, <a href="https://doi.org/10.5281/zenodo.21519796" rel="nofollow" target="_blank">10.5281/zenodo.21519796</a>, which always resolves to the latest version, through its own <a href="https://openwashdata.github.io/solidwastekampala/" rel="nofollow" target="_blank">website</a>, and through schema.org metadata.</li>
<li><strong>Accessible</strong> as an R package, as CSV or XLSX files, and as a Zenodo archive that does not depend on GitHub.</li>
<li><strong>Interoperable</strong> because the data are tidy, the names follow one convention, ordered categories are stored as ordered factors, and the files use UTF-8.</li>
<li><strong>Reusable</strong> because the dictionary describes all 37 variables with units, the processing script reproduces the data, the inconsistencies of the source are documented, and the data carry a CC BY 4.0 licence. Version 1.0.1 of 17 September carries the ORCID iDs of all eight authors.</li>
</ul>
<p>There is one more layer that the FAIR principles do not name, the record of how the package was made. The repository archives every prompt in <code>prompts/</code> and every plan in <code>plans/</code>, and commit messages link to the prompts behind them.</p>
</section>
<section id="what-i-take-from-it" class="level2">
<h2 class="anchored" data-anchor-id="what-i-take-from-it">What I take from it</h2>
<p>Cleaning the data was the smallest part. Most of the work went into the plan, the dictionary, the documentation, the metadata, the review, the citation and the exchange with the authors. That work turns a spreadsheet into a publication, and it is the reason why so many datasets never leave the hard drive. With the tools we have built over the years, a versioned review standard and an AI assistant that works through checklists, it now fits into a day.</p>
</section>
<section id="how-i-used-ai" class="level2">
<h2 class="anchored" data-anchor-id="how-i-used-ai">How I used AI</h2>
<p>I used AI for this package and for this post, and the record of that use is public.</p>
<p><strong>The package.</strong> Claude Code (model Claude Fable 5) wrote the processing script, the dictionary, the documentation and the metadata, following my prompts. The repository archives the 11 prompts I sent up to the release, and 14 of the 39 commits made by 24 July carry an <code>Assisted-by</code> line that names the model. I reviewed and merged each of the eight pull requests, handled the exchange with the authors and settled the spelling of their names. The processing script is plain R code, and anyone can rerun it without any AI tool and get the same data.</p>
<p><strong>This post.</strong> Claude (model Claude Fable 5) wrote the first draft on 24 July from the commits, issues, pull requests and prompts. On 7 October and 8 October Claude (model Claude Opus 5.5) revised and shortened it at my request. I chose what the post says, checked every date and number against the repository, and decided to publish it.</p>
<p>The prompts, the commits and this post are public, so anyone can check this statement against the record.</p>
<p><em>The solidwastekampala package was published by <a href="https://openwashdata.org/" rel="nofollow" target="_blank">openwashdata</a> with funding from the Open Research Data Program of the ETH Board. The data come from Katukiza et al. at Makerere University. You can explore them at <a href="https://openwashdata.github.io/solidwastekampala/" rel="nofollow" target="_blank">openwashdata.github.io/solidwastekampala</a>.</em></p>


</section>

 
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://openwashdata.org/pages/blog/posts/2026-10-20-solidwastekampala-story/"> openwashdata</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/from-a-raw-spreadsheet-to-a-published-data-package-in-a-day/">From a raw spreadsheet to a published data package in a day</a>]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">404129</post-id>	</item>
		<item>
		<title>Choosing the size of the calibration set in conformal prediction with ahead::conformalize()</title>
		<link>https://www.r-bloggers.com/2026/10/choosing-the-size-of-the-calibration-set-in-conformal-prediction-with-aheadconformalize/</link>
		
		<dc:creator><![CDATA[T. Moudiki]]></dc:creator>
		<pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://thierrymoudiki.github.io//blog/2026/10/18/r/conformalize-split-prob</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> This post explores the impact of the split_prob parameter in the ahead::conformalize() function on the resulting prediction intervals.</div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/choosing-the-size-of-the-calibration-set-in-conformal-prediction-with-aheadconformalize/">Choosing the size of the calibration set in conformal prediction with ahead::conformalize()</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://thierrymoudiki.github.io//blog/2026/10/18/r/conformalize-split-prob"> T. Moudiki's Webpage - R</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
<p>In version 0.39.0 of the <a href="https://github.com/Techtonique/ahead" rel="nofollow" target="_blank"><code>ahead</code></a> package, the <code>conformalize()</code> function has been updated to include a new parameter called <code>split_prob</code>. This parameter allows users to specify the proportion of the data that should be used for calibration when constructing conformal prediction intervals.</p>

<h1 id="0---install-packages">0 &#8211; Install packages</h1>

<pre>install.packages(&quot;pak&quot;)

pak::pak(&quot;Techtonique/ahead&quot;)

library(ahead)
</pre>

<h1 id="1---usaccdeaths">1 &#8211; <code>USAccDeaths</code></h1>

<pre>par(mfrow=c(2, 2))
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = USAccDeaths,
                           h = 20L, split_prob = 0.4)
plot(obj)
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = USAccDeaths,
                           h = 20L, split_prob = 0.3)
plot(obj)
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = USAccDeaths,
                           h = 20L, split_prob = 0.6)
plot(obj)
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = USAccDeaths,
                           h = 20L, split_prob = 0.7)
plot(obj)
</pre>

<p><img src="https://i2.wp.com/thierrymoudiki.github.io/images/2026-10-18/2026-10-18-conformalize-split-prob_5_0.png?w=578&#038;ssl=1" alt="image-title-here" class="img-responsive" data-recalc-dims="1" /></p>

<h1 id="2---airpassengers">2 - <code>AirPassengers</code></h1>

<pre>par(mfrow=c(2, 2))
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = AirPassengers,
                           h = 20L, split_prob = 0.4)
plot(obj)
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = AirPassengers,
                           h = 20L, split_prob = 0.3)
plot(obj)
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = AirPassengers,
                           h = 20L, split_prob = 0.6)
plot(obj)
obj &lt;- ahead::conformalize(FUN = forecast::ets, y = AirPassengers,
                           h = 20L, split_prob = 0.7)
plot(obj)
</pre>

<p><img src="https://i0.wp.com/thierrymoudiki.github.io/images/2026-10-18/2026-10-18-conformalize-split-prob_7_0.png?w=578&#038;ssl=1" alt="image-title-here" class="img-responsive" data-recalc-dims="1" /></p>

<h1 id="3---fdeaths">3 - <code>fdeaths</code></h1>

<pre>par(mfrow=c(2, 2))
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = fdeaths,
                           h = 20L, split_prob = 0.4)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = fdeaths,
                           h = 20L, split_prob = 0.3)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = fdeaths,
                           h = 20L, split_prob = 0.6)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = fdeaths,
                           h = 20L, split_prob = 0.7)
plot(obj)
</pre>

<p><img src="https://i1.wp.com/thierrymoudiki.github.io/images/2026-10-18/2026-10-18-conformalize-split-prob_9_0.png?w=578&#038;ssl=1" alt="image-title-here" class="img-responsive" data-recalc-dims="1" /></p>

<h1 id="4---ukdriverdeaths">4 - <code>UKDriverDeaths</code></h1>

<pre>par(mfrow=c(2, 2))
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = UKDriverDeaths,
                           h = 20L, split_prob = 0.4)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = UKDriverDeaths,
                           h = 20L, split_prob = 0.3)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = UKDriverDeaths,
                           h = 20L, split_prob = 0.6)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = UKDriverDeaths,
                           h = 20L, split_prob = 0.7)
plot(obj)
</pre>

<p><img src="https://i0.wp.com/thierrymoudiki.github.io/images/2026-10-18/2026-10-18-conformalize-split-prob_11_0.png?w=578&#038;ssl=1" alt="image-title-here" class="img-responsive" data-recalc-dims="1" /></p>

<h1 id="5---wwwusage">5 - <code>WWWusage</code></h1>

<pre>par(mfrow=c(2, 2))
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = WWWusage,
                           h = 20L, split_prob = 0.4)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = WWWusage,
                           h = 20L, split_prob = 0.3)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = WWWusage,
                           h = 20L, split_prob = 0.6)
plot(obj)
obj &lt;- ahead::conformalize(FUN = ahead::dynrmf, y = WWWusage,
                           h = 20L, split_prob = 0.5)
plot(obj)
</pre>

<p><img src="https://i2.wp.com/thierrymoudiki.github.io/images/2026-10-18/2026-10-18-conformalize-split-prob_13_0.png?w=578&#038;ssl=1" alt="image-title-here" class="img-responsive" data-recalc-dims="1" /></p>


<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://thierrymoudiki.github.io//blog/2026/10/18/r/conformalize-split-prob"> T. Moudiki's Webpage - R</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/choosing-the-size-of-the-calibration-set-in-conformal-prediction-with-aheadconformalize/">Choosing the size of the calibration set in conformal prediction with ahead::conformalize()</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404305</post-id>	</item>
		<item>
		<title>Estimate your BAC, now 100% in the browser</title>
		<link>https://www.r-bloggers.com/2026/10/estimate-your-bac-now-100-in-the-browser/</link>
		
		<dc:creator><![CDATA[R on Publishable Stuff]]></dc:creator>
		<pubDate>Sat, 17 Oct 2026 22:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://sumsar.net/blog/drinkr-in-the-browser/</guid>

					<description><![CDATA[<p>Thanks to the awesomeness of<br />
  webR and<br />
  Shinylive,<br />
  the tool I made in back in 2014 to estimate your blood alcohol concentration (BAC) now runs 100% in the browser, no server needed!</p>
<p>  Try the browser version of drinkR here!</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/estimate-your-bac-now-100-in-the-browser/">Estimate your BAC, now 100% in the browser</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://sumsar.net/blog/drinkr-in-the-browser/"> R on Publishable Stuff</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
<p>Thanks to the awesomeness of 
  <a href="https://docs.r-wasm.org/webr/latest/" rel="nofollow" target="_blank">webR</a> and 
  <a href="https://github.com/posit-dev/r-shinylive" rel="nofollow" target="_blank">Shinylive</a>, 
  <a href="https://rasmusab.github.io/drinkr/" rel="nofollow" target="_blank">the tool</a> I made in back in 2014 to estimate your blood alcohol concentration (BAC) now runs 100% in the browser, no server needed!</p>
<p>
  <a href="https://rasmusab.github.io/drinkr/" rel="nofollow" target="_blank">Try the browser version of drinkR here!</a></p>
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://sumsar.net/blog/drinkr-in-the-browser/"> R on Publishable Stuff</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/estimate-your-bac-now-100-in-the-browser/">Estimate your BAC, now 100% in the browser</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404291</post-id>	</item>
		<item>
		<title>tsmp Is Back: A Faster, Friendlier Matrix Profile Workflow for R</title>
		<link>https://www.r-bloggers.com/2026/10/tsmp-is-back-a-faster-friendlier-matrix-profile-workflow-for-r/</link>
		
		<dc:creator><![CDATA[r-bloggers on Francisco Bischoff]]></dc:creator>
		<pubDate>Fri, 16 Oct 2026 15:15:46 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">/posts/matrixprofiler-and-tsmp-major-updates/</guid>

					<description><![CDATA[<p>A new chapter for the Matrix Profile in R<br />
For a while, tsmp looked quiet. The project was not standing still, though: the Matrix Profile tools for R have been reorganized around a much clearer split between the computational engine and the everyday data-mining interface.<br />
In tsmp 0.5.1, the package that ...</p>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/tsmp-is-back-a-faster-friendlier-matrix-profile-workflow-for-r/">tsmp Is Back: A Faster, Friendlier Matrix Profile Workflow for R</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://franzbischoff.rbind.io/posts/matrixprofiler-and-tsmp-major-updates/"> r-bloggers on Francisco Bischoff</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>



<style type="text/css">
img {
  display: block; margin-left: auto; margin-right: auto;
}
</style>
<div id="a-new-chapter-for-the-matrix-profile-in-r" class="section level2">
<h2>A new chapter for the Matrix Profile in R</h2>
<p>For a while, <code>tsmp</code> looked quiet. The project was not standing still, though: the Matrix Profile tools for R have been reorganized around a much clearer split between the computational engine and the everyday data-mining interface.</p>
<p>In <code>tsmp</code> 0.5.1, the package that many of us have used for years becomes a pure-R analytical layer. The new <a href="https://cran.r-project.org/package=matrixprofiler" rel="nofollow" target="_blank"><code>matrixprofiler</code></a> package takes over the computationally intensive work in an optimized C++17 engine. The result is not a replacement for <code>tsmp</code>; it is a stronger foundation underneath it.</p>
</div>
<div id="two-packages-two-jobs" class="section level2">
<h2>Two packages, two jobs</h2>
<p>The older <code>tsmp</code> release brought algorithms, analysis helpers, and much of the computation together in one package. That made it a convenient starting point, but as series and workloads grew, computational work implemented around R-level operations could become the bottleneck.</p>
<p>The new architecture separates those responsibilities:</p>
<ul>
<li><strong><code>matrixprofiler</code></strong> provides the low-level Matrix Profile algorithms and optimized numerical primitives, with native multithreading through <code>RcppThread</code>.</li>
<li><strong><code>tsmp</code></strong> keeps the higher-level workflow close to the analyst: motifs, chains, segmentation, snippets, plotting, and the familiar tools for day-to-day time-series mining.</li>
</ul>
<p>The heavy computation now runs in the native engine, while the package you use to explore and interpret results stays approachable. <code>tsmp</code> itself no longer needs compilation when installed from source; it delegates its computational backend to <code>matrixprofiler</code>.</p>
<p>The everyday workflow remains familiar:</p>
<pre>library(tsmp)

profile &lt;- tsmp(series, window_size = 80)
motifs &lt;- find_motif(profile, n_motifs = 3)
plot(motifs)</pre>
<p>The difference is what happens under the hood: computationally intensive steps can use the native backend and its <code>n_workers</code> controls, without asking every analyst to work directly with low-level routines.</p>
</div>
<div id="from-a-contrast-score-to-a-useful-pattern" class="section level2">
<h2>From a contrast score to a useful pattern</h2>
<p>The new engine also adds a native implementation of the <strong>Contrast Profile</strong>. It is designed for a practical question: <em>which subsequence is repeated in the examples I care about, but is absent from the examples I do not care about?</em></p>
<p>Given positive and negative time series, the Contrast Profile compares each positive subsequence’s nearest match in the negative data with its nearest non-trivial match in the positive data. In simplified notation,</p>
<p><span class="math display">\[
C_m = \frac{MP_m^{(+,-)} - MP_m^{(+,+)}}{\sqrt{2m}},
\]</span></p>
<p>where <span class="math inline">\(m\)</span> is the window size. A high score highlights a pattern that is well conserved within the positive class and unlike the negative class. The original paper calls the highest-scoring subsequence the <strong>Plato</strong>: a candidate signature for the behavior or class of interest.</p>
<p>In R, <code>matrixprofiler::contrast()</code> computes this profile directly:</p>
<pre>cp &lt;- matrixprofiler::contrast(negative_data = negative_series, positive_data = positive_series,
    window_size = 80L, distance = &quot;pearson&quot;, n_workers = 4L)

cp$plato
cp$plato_idx</pre>
<p>The original Contrast Profile paper also discusses <strong>Top-K Platos</strong> for cases where the target behavior has more than one shape. Its key idea is important: the next result should be discovered after accounting for the previous pattern, rather than simply taking the next-highest, potentially redundant peak.</p>
<p>The current package does not provide a <code>contrast_top_k()</code> function. For now, users who need several Contrast Profile Platos must implement the iterative orchestration themselves: discover a Plato with <code>contrast()</code>, add its pattern to the negative examples, and recompute before looking for the next one. The expensive Contrast Profile calculation still runs in the native <code>matrixprofiler</code> engine; the Top-K loop is left to the user.</p>
</div>
<div id="rfcp-when-frequency-matters" class="section level2">
<h2>RFCP: when frequency matters</h2>
<p>The new <strong>Relative Frequency Contrast Profile (RFCP)</strong> takes this contrastive idea further. Instead of comparing only the nearest positive and negative matches, RFCP considers ordered ranks of non-overlapping neighbors. This lets it ask not just whether a pattern occurs, but how its recurrence differs between positive and negative data.</p>
<p><code>matrixprofiler::rfcp()</code> returns the contrast profile and the highest-scoring Plato. For a ranked, diverse set of discoveries, <code>matrixprofiler::rfcp_top_k()</code> iteratively finds a pattern, suppresses its occurrences and trivial neighbors, and then searches for the next one. You can request a different frequency rank at each iteration, enable adaptive frequency selection, and use callbacks to checkpoint longer runs.</p>
<pre>top_patterns &lt;- matrixprofiler::rfcp_top_k(positive_data = positive_series, negative_data = negative_series,
    window_size = 80L, k_platos = 3L, max_freq = c(4L, 3L, 2L), adaptive_max_freq = TRUE, n_workers = 4L)

top_patterns$platos</pre>
<p>Here, Top-K means a sequence of distinct, contrastive morphologies, not simply the K tallest overlapping peaks. That makes the result more useful when a class can be expressed in several ways, or when the first discovery would otherwise dominate the rest of the search.</p>
</div>
<div id="optional-cuda-acceleration" class="section level2">
<h2>Optional CUDA acceleration</h2>
<p>The standard <code>matrixprofiler</code> installation is a portable, multithreaded CPU engine. For supported algorithms, an NVIDIA GPU is also an option: the project documents a manual CUDA-enabled build, including support for algorithms such as MPX and RFCP.</p>
<p>With the CUDA Toolkit and a compatible NVIDIA setup, clone the repository and install through its build script:</p>
<pre>git clone https://github.com/matrix-profile-foundation/matrixprofiler.git
cd matrixprofiler
./inst/cuda/build_cuda_rcpp.sh R CMD INSTALL .</pre>
<p>After installation, check whether the package was compiled with CUDA support:</p>
<pre>matrixprofiler::has_cuda()</pre>
<p>CUDA is optional and must be enabled at build time; installing the regular CRAN package does not automatically turn it on.</p>
</div>
<div id="a-practical-way-to-get-started" class="section level2">
<h2>A practical way to get started</h2>
<p>If you already use <code>tsmp</code>, start with the new <a href="https://github.com/matrix-profile-foundation/tsmp/blob/master/MIGRATION.md" rel="nofollow" target="_blank">migration guide</a>, especially if your scripts create <code>doSNOW</code> clusters or assume invalid profile indices are <code>-1</code>. The updated parallel interface uses <code>n_workers</code>, and missing indices now use R’s <code>NA_integer_</code> convention.</p>
<p>For new analyses, install the familiar high-level tools and their computational backend:</p>
<pre>install.packages(c(&quot;tsmp&quot;, &quot;matrixprofiler&quot;))</pre>
<p><code>tsmp</code> remains the friendly place to do everyday Matrix Profile data mining. <code>matrixprofiler</code> makes that experience faster and provides a home for optimized algorithms that need more direct control. Together, they bring the R ecosystem back into active development and make room for more methods without making the daily workflow harder to use.</p>
<p>Explore <a href="https://cran.r-project.org/package=tsmp" rel="nofollow" target="_blank"><code>tsmp</code> on CRAN</a>, <a href="https://cran.r-project.org/package=matrixprofiler" rel="nofollow" target="_blank"><code>matrixprofiler</code> on CRAN</a>, and the <a href="https://matrix-profile-foundation.github.io/matrixprofiler/" rel="nofollow" target="_blank">matrixprofiler documentation</a>. For the research behind the Contrast Profile, see Mercer et al., <a href="https://doi.org/10.1109/ICDM51629.2021.00151" rel="nofollow" target="_blank">“Matrix Profile XXIII: Contrast Profile: A Novel Time Series Primitive that Allows Real World Classification”</a>.</p>
</div>

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://franzbischoff.rbind.io/posts/matrixprofiler-and-tsmp-major-updates/"> r-bloggers on Francisco Bischoff</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/tsmp-is-back-a-faster-friendlier-matrix-profile-workflow-for-r/">tsmp Is Back: A Faster, Friendlier Matrix Profile Workflow for R</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404263</post-id>	</item>
		<item>
		<title>[Statistics] Collinearity in regression</title>
		<link>https://www.r-bloggers.com/2026/10/statistics-collinearity-in-regression/</link>
		
		<dc:creator><![CDATA[R on Zhenguo Zhang&#039;s Blog]]></dc:creator>
		<pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://fortune9.netlify.app/2026/10/16/statistics-collinearity-in-regression/</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; "> Zhenguo Zhang's Blog https://fortune9.netlify.app/2026/10/16/statistics-collinearity-in-regression/ -</p>
<p>library(knitr)<br />
library(ggplot2)<br />
library(dplyr)<br />
library(tidyr)<br />
library(broom)</p>
<p>Problem<br />
When performing regression analysis, you may encounter two or ...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/statistics-collinearity-in-regression/">[Statistics] Collinearity in regression</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://fortune9.netlify.app/2026/10/16/statistics-collinearity-in-regression/"> R on Zhenguo Zhang&#039;s Blog</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
Zhenguo Zhang&#8217;s Blog https://fortune9.netlify.app/2026/10/16/statistics-collinearity-in-regression/ &#8211;


<pre>library(knitr)
library(ggplot2)
library(dplyr)
library(tidyr)
library(broom)</pre>
<div id="problem" class="section level2">
<h2>Problem</h2>
<p>When performing regression analysis, you may encounter two or more predictors that are highly correlated. This is called collinearity or multicollinearity. It can cause problems in estimating the regression coefficients and interpreting the results.</p>
<p>In this post, I will show how collinearity can affect regression analysis, how to detect
collinearity, and how to deal with it.</p>
</div>
<div id="example-one-predictor-is-a-linear-function-of-another-predictor" class="section level2">
<h2>Example: one predictor is a linear function of another predictor</h2>
<p>Let’s simulate data with the following variables:</p>
<ul>
<li><code>X1</code>: a random variable drawn from a normal distribution with mean 0 and standard deviation 1.</li>
<li><code>X2</code>: another random variable drawn from a normal distribution with mean 0 and standard deviation 1, independent of <code>X1</code>.</li>
<li><code>Z</code>: a variable linearly related to <code>X2</code>, defined as <code>Z = 5*X2 + noise</code>, where the noise is drawn from a normal distribution with mean 0 and standard deviation 1.</li>
<li><code>Y</code>: a linear combination of <code>X1</code> and <code>X2</code> plus noise, defined as <code>Y = 3*X1 + 2*X2 + noise</code>, where the noise is drawn from a normal distribution with mean 0 and standard deviation 1.</li>
</ul>
<pre>set.seed(123)
n &lt;- 100
X1 &lt;- rnorm(n, mean = 0, sd = 1)
X2 &lt;- rnorm(n, mean = 0, sd = 1)
Z &lt;- 5 * X2 + rnorm(n, mean = 0, sd = 1)
Y &lt;- 3 * X1 + 2 * X2 + rnorm(n, mean = 0, sd = 1)</pre>
<p>The correlation coefficients among the three predictors are:</p>
<pre>kable(cor(cbind(X1, X2, Z)), caption=&quot;Correlation of the predictors&quot;)</pre>
<table>
<caption><span id="tab:unnamed-chunk-3">Table 1: </span>Correlation of the predictors</caption>
<thead>
<tr class="header">
<th align="left"></th>
<th align="right">X1</th>
<th align="right">X2</th>
<th align="right">Z</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td align="left">X1</td>
<td align="right">1.0000000</td>
<td align="right">-0.0495321</td>
<td align="right">-0.0730836</td>
</tr>
<tr class="even">
<td align="left">X2</td>
<td align="right">-0.0495321</td>
<td align="right">1.0000000</td>
<td align="right">0.9814767</td>
</tr>
<tr class="odd">
<td align="left">Z</td>
<td align="right">-0.0730836</td>
<td align="right">0.9814767</td>
<td align="right">1.0000000</td>
</tr>
</tbody>
</table>
<p>Now let’s fit two linear regression models: one with <code>X1</code> and <code>X2</code> as predictors, and another with <code>X1</code>, <code>X2</code>, and <code>Z</code> as predictors.
We will then examine how the following metrics change: coefficients, standard errors, t-values, p-values, R-squared, adjusted R-squared.</p>
<pre>fit1 &lt;- lm(Y ~ X1 + X2)
fit2 &lt;- lm(Y ~ X1 + X2 + Z)
coef_results &lt;- rbind(
  tidy(fit1) %&gt;% mutate(model = &quot;fit1&quot;),
  tidy(fit2) %&gt;% mutate(model = &quot;fit2&quot;)
)
# move model to the first column
coef_results &lt;- coef_results %&gt;% select(model, everything())
# now R-squared and adjusted R-squared
rsq_results &lt;- rbind(
  glance(fit1) %&gt;% mutate(model = &quot;fit1&quot;),
  glance(fit2) %&gt;% mutate(model = &quot;fit2&quot;)
) %&gt;%
  select(model, everything())
kable(coef_results, caption = &quot;The coefficients and statistics&quot;)</pre>
<table>
<caption><span id="tab:unnamed-chunk-5">Table 2: </span>The coefficients and statistics</caption>
<thead>
<tr class="header">
<th align="left">model</th>
<th align="left">term</th>
<th align="right">estimate</th>
<th align="right">std.error</th>
<th align="right">statistic</th>
<th align="right">p.value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td align="left">fit1</td>
<td align="left">(Intercept)</td>
<td align="right">-0.0270771</td>
<td align="right">0.1058626</td>
<td align="right">-0.2557758</td>
<td align="right">0.7986656</td>
</tr>
<tr class="even">
<td align="left">fit1</td>
<td align="left">X1</td>
<td align="right">2.9521917</td>
<td align="right">0.1154749</td>
<td align="right">25.5656651</td>
<td align="right">0.0000000</td>
</tr>
<tr class="odd">
<td align="left">fit1</td>
<td align="left">X2</td>
<td align="right">2.0448517</td>
<td align="right">0.1090059</td>
<td align="right">18.7590842</td>
<td align="right">0.0000000</td>
</tr>
<tr class="even">
<td align="left">fit2</td>
<td align="left">(Intercept)</td>
<td align="right">-0.0193261</td>
<td align="right">0.1073437</td>
<td align="right">-0.1800395</td>
<td align="right">0.8575011</td>
</tr>
<tr class="odd">
<td align="left">fit2</td>
<td align="left">X1</td>
<td align="right">2.9445494</td>
<td align="right">0.1168766</td>
<td align="right">25.1936688</td>
<td align="right">0.0000000</td>
</tr>
<tr class="even">
<td align="left">fit2</td>
<td align="left">X2</td>
<td align="right">2.3331532</td>
<td align="right">0.5743463</td>
<td align="right">4.0622761</td>
<td align="right">0.0000993</td>
</tr>
<tr class="odd">
<td align="left">fit2</td>
<td align="left">Z</td>
<td align="right">-0.0573870</td>
<td align="right">0.1122308</td>
<td align="right">-0.5113302</td>
<td align="right">0.6102939</td>
</tr>
</tbody>
</table>
<pre>kable(rsq_results, caption = &quot;The goodness of fit&quot;)</pre>
<table>
<caption><span id="tab:unnamed-chunk-5">Table 2: </span>The goodness of fit</caption>
<colgroup>
<col width="5%" />
<col width="8%" />
<col width="12%" />
<col width="7%" />
<col width="8%" />
<col width="7%" />
<col width="2%" />
<col width="8%" />
<col width="7%" />
<col width="7%" />
<col width="7%" />
<col width="10%" />
<col width="4%" />
</colgroup>
<thead>
<tr class="header">
<th align="left">model</th>
<th align="right">r.squared</th>
<th align="right">adj.r.squared</th>
<th align="right">sigma</th>
<th align="right">statistic</th>
<th align="right">p.value</th>
<th align="right">df</th>
<th align="right">logLik</th>
<th align="right">AIC</th>
<th align="right">BIC</th>
<th align="right">deviance</th>
<th align="right">df.residual</th>
<th align="right">nobs</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td align="left">fit1</td>
<td align="right">0.9082614</td>
<td align="right">0.9063699</td>
<td align="right">1.047502</td>
<td align="right">480.1763</td>
<td align="right">0</td>
<td align="right">2</td>
<td align="right">-145.0117</td>
<td align="right">298.0234</td>
<td align="right">308.4441</td>
<td align="right">106.4342</td>
<td align="right">97</td>
<td align="right">100</td>
</tr>
<tr class="even">
<td align="left">fit2</td>
<td align="right">0.9085106</td>
<td align="right">0.9056516</td>
<td align="right">1.051513</td>
<td align="right">317.7673</td>
<td align="right">0</td>
<td align="right">3</td>
<td align="right">-144.8757</td>
<td align="right">299.7514</td>
<td align="right">312.7773</td>
<td align="right">106.1452</td>
<td align="right">96</td>
<td align="right">100</td>
</tr>
</tbody>
</table>
<p>As the results show, the first model, <code>fit1</code>, roughly captures the true relationship in the simulated data,
and both coefficients are highly statistically significant.</p>
<p>Adding <code>Z</code> to the regression model produces several changes: (1) the coefficient for <code>X2</code>
becomes smaller, (2) its standard error increases more than fivefold, and (3)
the p-value for the coefficient test consequently increases substantially. Overall,
the model’s R-squared changes little, but its AIC and BIC values increase,
suggesting that the model with three predictors is less favorable than the model with two predictors.</p>
<p>In summary, including a predictor that is highly correlated with an existing
predictor reduces the statistical significance of the existing predictor.</p>
</div>
<div id="example-two-predictors-with-different-levels-of-correlation" class="section level2">
<h2>Example: two predictors with different levels of correlation</h2>
<p>Now let’s simulate datasets containing two variables with different levels of
correlation. We will use <a href="https://en.wikipedia.org/wiki/Cholesky_decomposition" rel="nofollow" target="_blank">Cholesky decomposition</a>
to simulate the data.</p>
<p>First, let’s write a function that simulates data for a specified
correlation coefficient:</p>
<pre>simulate_correlated_data&lt;-function(rho, n=100, seed=NULL) {
  if(is.null(seed)) {
    seed &lt; Sys.time()
  }
  set.seed(seed)
  x &lt;- rnorm(n)
  z &lt;- rnorm(n)
  y &lt;- rho*x + sqrt(1-rho^2)*z
  return(cbind(x,y))
}</pre>
<p>Now let’s simulate five datasets with target correlations of
0.1, 0.3, 0.5, 0.8, and 0.95 between the two predictors. We will also make both predictors
contribute to the response variable so that we can examine how different levels of correlation
affect the regression fit:</p>
<pre>n&lt;-100
rhos&lt;-c(0.1, 0.3, 0.5, 0.8, 0.95)
simulated_data &lt;- lapply(rhos, simulate_correlated_data, n=n)
# let&#39;s calculate the correlation coefficients for the simulated data
simulated_rhos&lt;-sapply(simulated_data, function(d) cor(d[,&quot;x&quot;],d[, &quot;y&quot;]))
kable(cbind(expected_rho=rhos, actual_rho=simulated_rhos))</pre>
<table>
<thead>
<tr class="header">
<th align="right">expected_rho</th>
<th align="right">actual_rho</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td align="right">0.10</td>
<td align="right">-0.0271945</td>
</tr>
<tr class="even">
<td align="right">0.30</td>
<td align="right">0.3337500</td>
</tr>
<tr class="odd">
<td align="right">0.50</td>
<td align="right">0.5365344</td>
</tr>
<tr class="even">
<td align="right">0.80</td>
<td align="right">0.8167663</td>
</tr>
<tr class="odd">
<td align="right">0.95</td>
<td align="right">0.9624687</td>
</tr>
</tbody>
</table>
<p>The correlation coefficient, rho, in each simulated dataset may not exactly
match the specified value because of random sampling, but we can still examine how the level of correlation affects
the regression.</p>
<pre> fits &lt;- lapply(simulated_data, function(d) {
          x1&lt;-d[,&quot;x&quot;]
          x2&lt;-d[, &quot;y&quot;]
          y &lt;- 10*x1 + 3*x2 + rnorm(n)
          fit&lt;-lm(y~x1 + x2)
          return(fit)
          } 
          )
coef_results&lt;-lapply(seq_along(rhos),
                     function(i) {
                       tidy(fits[[i]]) %&gt;%
                         mutate(rho = rhos[i]) %&gt;%
                         select(rho, everything())
                     }
                     )
coef_results &lt;- do.call(rbind, coef_results)
kable(coef_results, caption = &quot;Regression statistics with different correlation strengths among predictors&quot;)</pre>
<table>
<caption><span id="tab:unnamed-chunk-8">Table 3: </span>Regression statistics with different correlation strengths among predictors</caption>
<thead>
<tr class="header">
<th align="right">rho</th>
<th align="left">term</th>
<th align="right">estimate</th>
<th align="right">std.error</th>
<th align="right">statistic</th>
<th align="right">p.value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td align="right">0.10</td>
<td align="left">(Intercept)</td>
<td align="right">0.1545731</td>
<td align="right">0.1093261</td>
<td align="right">1.4138713</td>
<td align="right">0.1606012</td>
</tr>
<tr class="even">
<td align="right">0.10</td>
<td align="left">x1</td>
<td align="right">10.0038407</td>
<td align="right">0.1047198</td>
<td align="right">95.5296414</td>
<td align="right">0.0000000</td>
</tr>
<tr class="odd">
<td align="right">0.10</td>
<td align="left">x2</td>
<td align="right">3.0724520</td>
<td align="right">0.1118900</td>
<td align="right">27.4595781</td>
<td align="right">0.0000000</td>
</tr>
<tr class="even">
<td align="right">0.30</td>
<td align="left">(Intercept)</td>
<td align="right">0.0349345</td>
<td align="right">0.1183168</td>
<td align="right">0.2952627</td>
<td align="right">0.7684243</td>
</tr>
<tr class="odd">
<td align="right">0.30</td>
<td align="left">x1</td>
<td align="right">9.8936718</td>
<td align="right">0.1339565</td>
<td align="right">73.8573235</td>
<td align="right">0.0000000</td>
</tr>
<tr class="even">
<td align="right">0.30</td>
<td align="left">x2</td>
<td align="right">3.0068927</td>
<td align="right">0.1113956</td>
<td align="right">26.9929282</td>
<td align="right">0.0000000</td>
</tr>
<tr class="odd">
<td align="right">0.50</td>
<td align="left">(Intercept)</td>
<td align="right">0.1417142</td>
<td align="right">0.0972655</td>
<td align="right">1.4569831</td>
<td align="right">0.1483507</td>
</tr>
<tr class="even">
<td align="right">0.50</td>
<td align="left">x1</td>
<td align="right">10.0727790</td>
<td align="right">0.1156101</td>
<td align="right">87.1271918</td>
<td align="right">0.0000000</td>
</tr>
<tr class="odd">
<td align="right">0.50</td>
<td align="left">x2</td>
<td align="right">2.9767374</td>
<td align="right">0.1173528</td>
<td align="right">25.3657122</td>
<td align="right">0.0000000</td>
</tr>
<tr class="even">
<td align="right">0.80</td>
<td align="left">(Intercept)</td>
<td align="right">-0.0609381</td>
<td align="right">0.0979742</td>
<td align="right">-0.6219812</td>
<td align="right">0.5354135</td>
</tr>
<tr class="odd">
<td align="right">0.80</td>
<td align="left">x1</td>
<td align="right">10.3154854</td>
<td align="right">0.1709446</td>
<td align="right">60.3440164</td>
<td align="right">0.0000000</td>
</tr>
<tr class="even">
<td align="right">0.80</td>
<td align="left">x2</td>
<td align="right">2.7154226</td>
<td align="right">0.1638409</td>
<td align="right">16.5735364</td>
<td align="right">0.0000000</td>
</tr>
<tr class="odd">
<td align="right">0.95</td>
<td align="left">(Intercept)</td>
<td align="right">-0.1158642</td>
<td align="right">0.0971670</td>
<td align="right">-1.1924234</td>
<td align="right">0.2360041</td>
</tr>
<tr class="even">
<td align="right">0.95</td>
<td align="left">x1</td>
<td align="right">10.0497393</td>
<td align="right">0.3537788</td>
<td align="right">28.4068467</td>
<td align="right">0.0000000</td>
</tr>
<tr class="odd">
<td align="right">0.95</td>
<td align="left">x2</td>
<td align="right">2.9023068</td>
<td align="right">0.3443712</td>
<td align="right">8.4278435</td>
<td align="right">0.0000000</td>
</tr>
</tbody>
</table>
<p>Let’s plot all the metrics:</p>
<pre>coef_results &lt;- coef_results %&gt;% 
    mutate(rho = factor(rho)) %&gt;%
    pivot_longer(
      cols = estimate:p.value,
      names_to = &quot;metric&quot;,
      values_to = &quot;value&quot;
    )
plt &lt;- ggplot(coef_results, aes(x=rho, y=value)) +
      geom_point(aes(color=term)) +
      facet_wrap(~ metric, ncol=2, scales=&quot;free_y&quot;) +
      theme(legend.position = &quot;top&quot;)
plt</pre>
<p><img src="https://i0.wp.com/fortune9.netlify.app/2026/10/16/statistics-collinearity-in-regression/index_files/figure-html/unnamed-chunk-10-1.png?w=450&#038;ssl=1" alt="" data-recalc-dims="1" /></p>
<p>The plots show the following patterns as the correlation between the predictors increases:</p>
<ul>
<li>The estimated coefficients change little, although some variation remains.</li>
<li>The p-values for both predictors remain very small.</li>
<li>The test statistics become smaller as rho increases.</li>
<li>The standard errors increase as rho increases.</li>
<li>Based on the last two observations, the p-values for the predictors should become larger, i.e., less significant.</li>
</ul>
<p>In summary, when the predictors in a regression are correlated, the test
for each coefficient becomes less statistically significant, even though the magnitudes
of the coefficients remain close to the values used to simulate the data.</p>
</div>
<div id="takeaways" class="section level2">
<h2>Takeaways</h2>
<ul>
<li>When one predictor, <code>Z</code>, is a linear function of another predictor, <code>X</code>, and has no direct
relationship with the response variable, including <code>Z</code> makes the test for the
true predictor <code>X</code> less statistically significant. The coefficient for <code>Z</code>
may not be significant because, after accounting for <code>X</code>, <code>Z</code> has no effect on <code>Y</code>.</li>
<li>When two predictors both affect <code>Y</code>, the statistical significance of their
coefficients decreases as the correlation between the predictors increases, even though the
magnitudes of the coefficients may remain close to the true values.</li>
</ul>
<p>Taken together, these examples suggest that you may want to exclude certain variables from your regression unless you
know they affect the response or want to examine the effects of other predictors
after controlling for those variables.</p>
</div>
- https://fortune9.netlify.app/2026/10/16/statistics-collinearity-in-regression/ - 
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://fortune9.netlify.app/2026/10/16/statistics-collinearity-in-regression/"> R on Zhenguo Zhang&#039;s Blog</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/statistics-collinearity-in-regression/">[Statistics] Collinearity in regression</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404274</post-id>	</item>
		<item>
		<title>Spring 2027 data science training courses: January to June</title>
		<link>https://www.r-bloggers.com/2026/10/spring-2027-data-science-training-courses-january-to-june/</link>
		
		<dc:creator><![CDATA[The Jumping Rivers Blog]]></dc:creator>
		<pubDate>Thu, 15 Oct 2026 23:59:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://www.jumpingrivers.com/blog/spring-2027-training-courses/</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; ">
<p>If you plan to learn R or Python in the new year, book before the end of November. Our spring 2027 data science training courses run from January to June, and early bird pricing on the first one ends on 29 November.<br />
There are 22 courses on the sc...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/spring-2027-data-science-training-courses-january-to-june/">Spring 2027 data science training courses: January to June</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://www.jumpingrivers.com/blog/spring-2027-training-courses/"> The Jumping Rivers Blog</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p>
<a href = "https://www.jumpingrivers.com/blog/spring-2027-training-courses/">
<img src="https://i0.wp.com/www.jumpingrivers.com/blog/spring-2027-training-courses/featured.png?w=400&#038;ssl=1" style="width:400px" class="image-center" style="display: block; margin: auto;" data-recalc-dims="1" />
</a>
</p>
<p>If you plan to learn R or Python in the new year, book before the end of November. Our spring 2027 data science training courses run from January to June, and early bird pricing on the first one ends on <strong>29 November</strong>.</p>
<p>There are 22 courses on the schedule. All of them run live online, and the trainers are data scientists and engineers who spend the rest of their week on client projects. Each course runs over two afternoons, 13:30 to 17:00 UK time. The Bayesian inference course is the exception: it runs over four.</p>
<h2 id="three-routes-through-our-spring-2027-training-courses">Three routes through our spring 2027 training courses</h2>
<p>The courses are scheduled so you can take them in order. There are three obvious routes, each ending with a set of skills you can use at work the following week.</p>
<h3 id="learning-r-from-scratch">Learning R from scratch</h3>
<p>Start with <a href="https://www.jumpingrivers.com/training/course/r-introduction-tidyverse-readr-ggplot2-dplyr/#event-2027-01-11-13-30-online" rel="nofollow" target="_blank">Introduction to R</a> on 11–12 January. <a href="https://www.jumpingrivers.com/training/course/data-tidyverse-dplyr-tidyr-lubridate-forcats/#event-2027-01-25-13-30-online" rel="nofollow" target="_blank">Data Wrangling in the Tidyverse</a> follows on 25–26 January, then <a href="https://www.jumpingrivers.com/training/course/r-advanced-graphics-ggplot2-plotly-themes-scaling-faceting/#event-2027-02-01-13-30-online" rel="nofollow" target="_blank">Data Visualisation with ggplot2</a> on 1–2 February and <a href="https://www.jumpingrivers.com/training/course/r-programming-functions-looping-conditionals/#event-2027-02-08-13-30-online" rel="nofollow" target="_blank">Programming with R</a> on 8–9 February. That gets you from your first script to writing your own functions in a month.</p>
<p><strong>Why take this route:</strong> Clean, chart and automate your data in code. Monthly reporting becomes a script you rerun in minutes, with fewer manual errors.</p>
<p>January too soon? The same four courses run again between 12 April and 11 May. If you are still deciding whether R is worth the time, we wrote about <a href="https://www.jumpingrivers.com/blog/why-learning-r-is-a-good-career-move-in-2026/" rel="nofollow" target="_blank">why learning R is a good career move</a>.</p>
<h3 id="learning-python">Learning Python</h3>
<p><a href="https://www.jumpingrivers.com/training/course/python-introduction-visualisation-manipulation/#event-2027-02-15-13-30-online" rel="nofollow" target="_blank">Introduction to Python</a> runs on 15–16 February. <a href="https://www.jumpingrivers.com/training/course/python-programming-control-flow-functions/#event-2027-02-22-13-30-online" rel="nofollow" target="_blank">Programming with Python</a> follows on 22–23 February, and <a href="https://www.jumpingrivers.com/training/course/python-matplotlib-seaborn-visualisation/#event-2027-03-01-13-30-online" rel="nofollow" target="_blank">Data Visualisation with Python</a> on 1–2 March. The same three run again in May and June.</p>
<p><strong>Why take this route:</strong> Analyse data in pandas, automate repeat tasks and produce report-ready charts, in the same language as your engineering team. It is also the groundwork for machine learning in Python.</p>
<h3 id="statistics-and-machine-learning-in-r">Statistics and machine learning in R</h3>
<p>This route is for people who already use R. <a href="https://www.jumpingrivers.com/training/course/r-statistics-modelling-linear-regression-clustering/#event-2027-03-08-13-30-online" rel="nofollow" target="_blank">Statistical Modelling with R</a> on 8–9 March covers hypothesis testing, regression, clustering and principal components analysis. <a href="https://www.jumpingrivers.com/training/course/r-prediction-inference-analytics-machine-learning-tidymodels/#event-2027-03-15-13-30-online" rel="nofollow" target="_blank">Machine Learning with Tidymodels</a> on 15–16 March and <a href="https://www.jumpingrivers.com/training/course/r-prediction-inference-tidymodels-lda-pre-processing-tree-based-models/#event-2027-03-22-13-30-online" rel="nofollow" target="_blank">Advanced Machine Learning with Tidymodels</a> on 22–23 March take you through pre-processing, model fitting and tuning.</p>
<p><strong>Why take this route:</strong> Build, validate and tune predictive models in tidymodels, and explain which variables drive them. Your team moves from reporting what happened to forecasting what happens next, with evidence for how far to trust it.</p>
<h2 id="two-courses-that-run-once-this-spring">Two courses that run once this spring</h2>
<p><a href="https://www.jumpingrivers.com/training/course/introduction-bayesian-inference-rstan-monte-carlo/#event-2027-01-18-13-30-online" rel="nofollow" target="_blank">Introduction to Bayesian Inference using RStan</a> runs over four afternoons, 18–21 January. You write Stan programs for a range of statistical models and learn to read the MCMC diagnostics in R. It is the only date for it in the first half of 2027.</p>
<p><a href="https://www.jumpingrivers.com/training/course/reporting-with-quarto/#event-2027-06-21-13-30-online" rel="nofollow" target="_blank">Reporting with Quarto</a> runs on 21–22 June. You build reports, slides and documents straight from R or Python code, so the numbers in the report change when the data does. It pairs well with the ideas in our post on <a href="https://www.jumpingrivers.com/blog/reproducible-analytical-pipelines/" rel="nofollow" target="_blank">reproducible analytical pipelines</a>.</p>
<aside class="advert">
<p>
Whether you want to start from scratch, or improve your skills, <a href="https://www.jumpingrivers.com/training/?utm_source=blog&#038;utm_medium=banner&#038;utm_campaign=2026-spring-2027-training-courses" rel="nofollow" target="_blank">Jumping Rivers has a training course for you</a>.
</p>
</aside>
<h2 id="early-bird-deadlines">Early bird deadlines</h2>
<p>Early bird pricing ends about six weeks before each course. Booking closes a week before the start date. The first three deadlines are:</p>
<ul>
<li>29 November for Introduction to R</li>
<li>6 December for Bayesian inference</li>
<li>13 December for Data Wrangling in the Tidyverse</li>
</ul>
<p>Dates for the second half of 2027 will follow.</p>
<h2 id="using-this-years-training-budget">Using this year’s training budget</h2>
<p>If your team has training budget left this December, booking a January course now spends it before the year ends. It also gets you the early bird price, because every January deadline falls before Christmas.</p>
<h2 id="what-you-get-on-every-course">What you get on every course</h2>
<ul>
<li>PDF notes and scripts to keep</li>
<li>Live demonstrations and hands-on exercises</li>
<li>Time with the trainer to ask questions about your own work</li>
</ul>
<p>We have run more than 1,000 courses, for organisations including NHS Scotland, Shell, Wessex Water and the Royal Statistical Society.</p>
<h2 id="training-a-whole-team">Training a whole team</h2>
<p>Every course in our <a href="https://www.jumpingrivers.com/training/all-courses/" rel="nofollow" target="_blank">full catalogue</a> can also run privately for one team, using your data and your tools. Group bookings and returning clients get a discount. Email <a href="mailto:training@jumpingrivers.com" rel="nofollow" target="_blank">training@jumpingrivers.com</a> with the size of the team and what they need to learn.</p>
<p><a href="https://www.jumpingrivers.com/training/public/" rel="nofollow" target="_blank">See the full public schedule and book a place</a>.</p>
<p>
For updates and revisions to this article, see the <a href = "https://www.jumpingrivers.com/blog/spring-2027-training-courses/">original post</a>
</p>
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://www.jumpingrivers.com/blog/spring-2027-training-courses/"> The Jumping Rivers Blog</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/spring-2027-data-science-training-courses-january-to-june/">Spring 2027 data science training courses: January to June</a>]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">404237</post-id>	</item>
		<item>
		<title>What is an MCP server?</title>
		<link>https://www.r-bloggers.com/2026/10/what-is-an-mcp-server/</link>
		
		<dc:creator><![CDATA[The Jumping Rivers Blog]]></dc:creator>
		<pubDate>Thu, 15 Oct 2026 23:59:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://www.jumpingrivers.com/blog/what-is-an-mcp-server/</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; ">
<p>The Model Context Protocol (MCP) is quietly<br />
becoming one of the most useful ideas in AI tooling.<br />
Here, we describe what MCP is, why it matters, and how to build an MCP server yourself.<br />
MCP is a standard way to link a Large Language Model (LLM) to the outside world.<br />
...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/what-is-an-mcp-server/">What is an MCP server?</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://www.jumpingrivers.com/blog/what-is-an-mcp-server/"> The Jumping Rivers Blog</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>

<p>
<a href = "https://www.jumpingrivers.com/blog/what-is-an-mcp-server/">
<img src="https://www.jumpingrivers.com/blog/what-is-an-mcp-server/" width="400" style="width:400px" class="image-center" style="display: block; margin: auto;" />
</a>
</p>
<style>
.blog-post :is(ul, ol) li {
margin-block: 0.2rem;
}
</style>
<p><a href="https://en.wikipedia.org/wiki/Model_Context_Protocol" rel="nofollow" target="_blank">The Model Context Protocol (MCP)</a> is quietly
becoming one of the most useful ideas in AI tooling.
Here, we describe what MCP is, why it matters, and how to build an MCP server yourself.</p>
<p>MCP is a standard way to link a Large Language Model (LLM) to the outside world.
An MCP server is a small piece of software that exposes some data or capability to an AI model.
Everyday examples include connecting your AI agent to your calendar or your file system, or allowing
it to search the web. These are things an LLM cannot do (or would struggle to do) by itself.
On its own, a model only knows what it learned during training, plus whatever you provide it, and it
has no way to check, fetch or act on anything beyond that.
Even then, it can only give out text responses, which you have to be the one to act on.
An MCP server can allow an LLM to carry out actions itself.
Less talking, more doing.</p>
<figure>
<img
src="https://www.jumpingrivers.com/blog/what-is-an-mcp-server/assets/llm-alone-vs-mcp-connected.webp"
alt="Two-panel diagram. Left: an LLM alone, with training data only, frozen at its training
cut-off with no way to check anything new. Right: an LLM connected via MCP to a database, email,
a filesystem and the internet, grounded in live, current data.">
<figcaption>
An LLM alone is frozen at its training cut-off. Connected via MCP, it can check, fetch and act
instead of just recalling.
</figcaption>
</figure>
<aside class="advert">
<p>
Join us for our AI in Production conference! For more details, check out our <a href="https://ai-in-production.jumpingrivers.com/" rel="nofollow" target="_blank">conference website!</a>
</p>
</aside>
<h2 id="why-not-just-use-an-api">Why not just use an API?</h2>
<!--- Clarify this paragraph ; NxM is opaque -->
<p>There are many ways to connect external capabilities to an AI model, usually via an API (Application
Programming Interface), and this was the industry standard.
However, APIs are all configured slightly differently, and once you start to scale up the number of
connections you need between multiple pieces of software, you hit a combinatorial problem: the
number of custom connections a product needs multiplies by the number of tools that use it.</p>
<p><a href="https://www.anthropic.com/news/model-context-protocol" rel="nofollow" target="_blank">Anthropic introduced MCP</a> in late 2024 as an
open standard for connecting tools and data to LLM applications. It works like a USB port on a
computer: any MCP server can be used with any agent/model, whether you are using Claude, Gemini,
ChatGPT or one you’ve made yourself.</p>
<figure>
<img
src="https://www.jumpingrivers.com/blog/what-is-an-mcp-server/assets/mcp-before-after-diagram-v2.webp"
alt="Before-and-after diagram. Before: five products each need their own custom connection to
three AI tools, giving 15 separate connections to build and maintain. After: all five products
connect through MCP, one standard layer, giving eight total connections through one protocol.">
<figcaption>MCP replaces a tangle of custom connections with one standard layer.</figcaption>
</figure>
<h2 id="building-your-own-mcp-server">Building your own MCP server</h2>
<p>Full instructions on how to build an MCP server are available in the
<a href="https://modelcontextprotocol.io/docs/develop/build-server" rel="nofollow" target="_blank">MCP documentation</a>, but here is a very
quick overview using Claude Code and the Sonnet 5 model.</p>
<p>The good news is that a working MCP server can be as short as a few lines of code.
Here’s the shape of it, step by step, using Python.</p>
<h3 id="step-1-install-the-dependencies">Step 1: Install the dependencies</h3>
<p>The
<a href="https://github.com/modelcontextprotocol" rel="nofollow" target="_blank">MCP project</a> maintains official SDKs for Python,
TypeScript and other languages.
The Python ‘mcp’ package embodies this SDK.
However, here we are going to use the ‘fastmcp’ package.
This is built upon ‘mcp’, but has some benefits (in particular, ‘fastmcp’-based tools can readily
be deployed on Posit Connect, see later).</p>
<pre>mkdir -p ~/projects/
cd ~/projects
uv init --python=3.15 dice-roll
# Initialized project `dice-roll`
# at `/home/&lt;user&gt;/projects/dice-roll`
cd dice-roll
uv add fastmcp
</pre><p>This gives us the following project structure, which is typical for a ‘uv’ project.
We will define our MCP server in <code>./src/dice_roll/__init__.py</code>, although if the project got much
larger, we would split it into separate modules or sub-packages.</p>
<pre>tree -L 2
.
├── pyproject.toml
├── README.md
├── src
│   └── dice_roll
└── uv.lock
</pre><h3 id="step-2-define-a-tool">Step 2: Define a tool</h3>
<p>An MCP server exposes “tools”: functions the model can call.
Here is a mini example: a server with a single tool that rolls a dice.</p>
<pre># src/dice_roll/__init__.py
from typing import Annotated
import random

from fastmcp import FastMCP
from pydantic import Field

# Give your server a name: this is what shows up when a model connects
mcp = FastMCP(&quot;dice-pick&quot;)

@mcp.tool
def roll_dice(
 sides: Annotated[int, Field(description=&quot;Number of sides on the dice&quot;, ge=2)] = 6,
) -&gt; int:
 &quot;&quot;&quot;Roll a single fair dice with the given number of sides and return the number rolled.&quot;&quot;&quot;
 return random.randint(1, sides)

def main() -&gt; None:
 mcp.run()
</pre><p>The <code>@mcp.tool</code> docstring (“Roll a single …”) is important.
This becomes the description of the tool that the LLM reads.
It is like the blurb of a book: the LLM will look at the description and from it decide whether it
wishes to use the tool or not.
If this description isn’t clear, it may lead to the tool not being used.</p>
<p>The type annotations for both the parameter <code>sides</code> and the value returned by <code>roll_dice()</code> also
provide useful metadata for the LLM.
Our <code>sides</code> parameter has a constraint that ensures at least 2 sides are present on the dice
(<code>ge=2</code>; this is enforced within Python by
<a href="https://pypi.org/project/pydantic/" rel="nofollow" target="_blank">Pydantic</a>).
When describing the tools provided by this MCP server, an LLM will be able to explain that the
minimum number of sides is 2 (and more advanced models may not call the tool otherwise).</p>
<h3 id="step-3-connect-it">Step 3: Connect it</h3>
<p>Save the code in the project, and we will test whether an MCP-compatible agent can use it.
There are a few ways to register our locally-defined MCP server with Claude Code.</p>
<p>Since we have used the <code>fastmcp</code> framework, there is a command line tool that installs the MCP
server for use from the project directory:</p>
<pre># From the ~/projects/dice-roll/ directory
uv run \
 fastmcp install claude-code src/dice_roll/__init__.py:mcp --with-editable .
</pre><p>The <code>uv run</code> part is necessary to ensure the correct Python environment is used.
The <code>fastmcp</code> command needs to find the <code>mcp</code> object that we have defined in our source code.
If we open <code>claude</code> and ask “What dice-pick tools do you know about?”, it replies:</p>
<pre>The dice-pick MCP server gives me one tool:
roll_dice: rolls one fair dice and returns the number it lands on.
┌───────────┬─────────┬─────────┬───────────────────┐
│ Parameter | Type │ Default │ Constraint │
├───────────┼─────────┼─────────┼───────────────────┤
│ sides │ integer │ 6 │ must be 2 or more │
└───────────┴─────────┴─────────┴───────────────────┘
So I can roll a d6, d20, d100 or any other single dice. It can&#39;t roll several dice in one call, add
modifiers or pick from a list. For something like 3d6, I&#39;d call it three times.
</pre><p>Then we can use the tool:</p>
<pre>❯ Roll a dice
Called dice-pick
I rolled a 6 on a standard six-sided dice.
...
❯ Roll a 20-side dice
Called dice-pick
I rolled a 20 on the d20.
That&#39;s the highest number on both rolls so far. [... blah blah blah ...]
...
❯ Roll a d12
Called dice-pick
I rolled a 4 on the d12, so the run of top scores is over.
</pre><p>We have installed our locally in-development MCP server on our machine, but it’s only usable from
the <code>~/projects/dice_roll</code> directory.
This is fine while you are testing, but you may want to use your MCP server in other locations once
it works well.</p>
<p>Our project is <code>uv</code>-based.
If we tell it which module and function should be called to start the project running, we can make
the project more easily usable from other locations.
But, the entry-point function used to start our MCP server running is <code>dice_roll.main()</code>, which <code>uv</code>
has already added as the entry-point to the project in the <code>pyproject.toml</code>:</p>
<pre>[project.scripts]
dice-roll = &quot;dice_roll:main&quot;
</pre><p>On running <code>uv run python</code> or <code>uv sync</code>, a <code>dice-roll</code> entry-point will be added to the virtual
environment for the project.</p>
<p>For Claude Code, we can then register our MCP server with a single command:</p>
<pre>claude mcp add --scope user dice-pick \
 -- /abs/path/to/projects/dice-roll/.venv/bin/dice-roll
</pre><p>Now, you can ask <code>claude</code> to “roll a 20-sided dice” from any directory on your computer and it will
call your tool directly rather than guessing at an answer.
This is possible because <code>claude mcp add ...</code> appends to the <code>mcpServers</code> entry of a file
(<code>~/.claude.json</code>).</p>
<p>A few tips before you build something bigger:</p>
<ul>
<li>Keep tool descriptions (the docstrings for your <code>@mcp.tool</code>-decorated functions) short and
specific. The model relies on the docstring to decide when to use a tool, so vague descriptions
lead to it being ignored or misused.</li>
<li>Think about what should require confirmation. Read-only tools (like looking something up) are
usually safe to let a model call freely; tools that change or delete something are worth gating
behind an explicit user confirmation step.</li>
</ul>
<p>You can remove the MCP server from your <code>claude</code> setup by using</p>
<pre>claude mcp remove dice-pick
</pre><p>Or just ask Claude to remove it!</p>
<h2 id="deploying-to-posit-connect">Deploying to Posit Connect</h2>
<p>A server running on your own machine is fine for testing, but it is only useful to you.
MCP servers can be deployed to a range of different cloud providers.
<a href="https://posit.co/products/enterprise/connect/" rel="nofollow" target="_blank">Posit Connect</a>, the platform we normally reach for
when publishing Shiny apps, Plumber APIs and Quarto reports, added first-class support for hosting
MCP servers in 2026, complete with a dedicated icon and its own tab in the content list.</p>
<p>Posit Connect does expect you to write your Python-based MCP servers using the <code>fastmcp</code> framework
that we used above.
Connect also needs to import a runnable application object that can handle HTTP-based requests.
When deployed, ‘fastmcp’-based MCP servers can expose an ASGI <code>app</code> object that handles these
requests.
Locally run servers use ‘stdio’ routing, but we only have to modify our current source code slightly
to deploy to Posit Connect.</p>
<pre># imports ...

mcp = ...

def roll_dice():
 ...

app = mcp.http_app() # For deployment on Connect

def main():
 mcp.run() # For running using stdio locally
</pre><p>For deployment of the MCP server to Posit Connect, we also need to create a <code>requirements.txt</code> file.
These are used to specify the run-time (and sometimes development) dependencies of a project.
Since we used <code>uv</code> to manage the project, this is easy to create:</p>
<pre>uv export --no-dev --no-hashes -o requirements.txt
</pre><p>The <code>requirements.txt</code> file contains a line <code>-e .</code> that dynamically includes any source code from
<code>src/</code>.
The Python environment that is built for your MCP server depends upon <code>requirements.txt</code> and is
rebuilt when that file is updated.</p>
<!---
Note that you need the "-e ." line, so that the fresh code is installed `.` causes cache problems
Because the environment within which your MCP is deployed is cache-keyed by requirements.txt
(So subsequent uploads where src/ changes won't invalidate the environment cache)
-->
<p>We use the <code>rsconnect-python</code> package (and the <code>rsconnect</code> command line tool that it installs) to
interact with Posit Connect.
You may first have to register with your Connect system using <code>rsconnect add ...</code> or
<code>rsconnect login ...</code>.</p>
<p>Then you can deploy with the <code>rsconnect</code> tool, using the FastAPI deployment mode and pointing the
entrypoint at the <code>app</code> object that we have just added to the source code.
This is done using a <code>--entrypoint</code> argument of the form <code>module:object_name</code>. Here this is
<code>dice_roll:app</code>, since our <code>app</code> object is in the <code>dice_roll</code> module:</p>
<pre>rsconnect deploy fastapi -n &lt;nickname-of-posit-server&gt; --entrypoint dice_roll:app ./
</pre><p>The Posit Publisher extension for VS Code and Positron works too, if you’d rather deploy from your
editor.
Posit has some <a href="https://docs.posit.co/connect/user/mcp-servers/" rel="nofollow" target="_blank">good documentation</a> on publishing
MCP servers.</p>
<p>On first deployment, Connect scans <code>requirements.txt</code>, recognises <code>fastmcp</code>, and automatically sets
the content type to MCP.
Once deployed, it keeps at least one process warm so there is no cold-start delay, and gives
it its own icon in the content list, with no extra configuration needed.</p>
<h2 id="using-your-deployed-mcp-server">Using your deployed MCP server</h2>
<p>To use an MCP server that is deployed in the cloud, the process is very similar to how we added our
locally-defined MCP server.
On Posit Connect (or wherever you have published), you will have a URL for the MCP server and you
can configure <code>claude</code> to use this URL as follows:</p>
<pre>claude mcp add --transport http &lt;mcp-name&gt; https://&lt;connect-server&gt;/content/&lt;content-hash&gt;/mcp
</pre><p>Here we used <code>dice-pick</code> as the name of the MCP server, consistent with the name in the source code.
When you next open <code>claude</code> you will be prompted to authenticate before you can use the tool.</p>
<p>Want to see this in action? Watch
<a href="https://www.youtube.com/watch?v=1mtWbxQE8S8" rel="nofollow" target="_blank">this walkthrough</a>, recorded live at our
<a href="https://aip.jmpr.io/" rel="nofollow" target="_blank">AI in Production 2026</a> conference, of an MCP server being built and used from
scratch.</p>
<h2 id="key-takeaways">Key takeaways</h2>
<ul>
<li>An MCP server is the standard way to give an AI model access to things it can’t do alone:
checking a calendar, reading a file, searching the web, or looking up live data.</li>
<li>It works like a USB port: build the connection once, and any compatible model can use it.</li>
<li>A working MCP server can be as short as a few lines of code. The model calls your tools directly
instead of guessing at an answer.</li>
<li>Keep tool descriptions short and specific, and gate anything destructive behind explicit
confirmation.</li>
<li>Posit Connect can host your MCP server directly, so your whole team can use it rather than just
your own machine.</li>
</ul>
<p>
For updates and revisions to this article, see the <a href = "https://www.jumpingrivers.com/blog/what-is-an-mcp-server/">original post</a>
</p>
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://www.jumpingrivers.com/blog/what-is-an-mcp-server/"> The Jumping Rivers Blog</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/what-is-an-mcp-server/">What is an MCP server?</a>]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">404235</post-id>	</item>
		<item>
		<title>Can we trust the correlation coefficient in designed field experiments?</title>
		<link>https://www.r-bloggers.com/2026/10/can-we-trust-the-correlation-coefficient-in-designed-field-experiments/</link>
		
		<dc:creator><![CDATA[Andrea Onofri]]></dc:creator>
		<pubDate>Thu, 15 Oct 2026 22:00:00 +0000</pubDate>
				<category><![CDATA[R bloggers]]></category>
		<guid isPermaLink="false">https://www.statforbiology.com/posts/general_CorrelationIndependence1.html</guid>

					<description><![CDATA[<div style = "width:60%; display: inline-block; float:left; ">
<p>One of the fundamental aims of field experiments is to explorare the relationships between several experimental traits, which have been recorded/measured altogheter for all the experimental units. For example, think about a genotype experiment w...</p></div>
<div style = "width: 40%; display: inline-block; float:right;"></div>
<div style="clear: both;"></div>
<strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/can-we-trust-the-correlation-coefficient-in-designed-field-experiments/">Can we trust the correlation coefficient in designed field experiments?</a>]]></description>
										<content:encoded><![CDATA[<!-- 
<div style="min-height: 30px;">
[social4i size="small" align="align-left"]
</div>
-->

<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 12px;">
[This article was first published on  <strong><a href="https://www.statforbiology.com/posts/general_CorrelationIndependence1.html"> Statforbiology</a></strong>, and kindly contributed to <a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers</a>].  (You can report issue about the content on this page <a href="https://www.r-bloggers.com/contact-us/">here</a>)
<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div>
 





<p>One of the fundamental aims of field experiments is to explorare the relationships between several experimental traits, which have been recorded/measured altogheter for all the experimental units. For example, think about a genotype experiment with 27 winter wheat genotypes in a Randomised Complete Block Design with three replicates. If we aim at assessing yield and yield quality, we have to measure, in each plot, several traits, such as grain yield and the weight of thousand kernels (TKW). Apart from separately analysing these two variables, we might be interested in describing their reciprocal relationships, by using the Pearson’s correlation coefficient. Let’s do this, by using the dataset ‘WheatQuality’, which is available in the ‘statforbiology’ package; we have 81 plots and just as many couples of measures in all.</p>
<div class="cell">
<pre>library(Hmisc)
library(dplyr)
library(statforbiology)
dataset &lt;- getAgroData(&quot;WheatQuality&quot;)
head(dataset)
##     Genotype Block Height  TKW Whectol Yield
## 1 arcobaleno     1     90 44.5    83.2 64.40
## 2 arcobaleno     2     90 42.8    82.2 60.58
## 3 arcobaleno     3     88 42.7    83.1 59.42
## 4       baio     1     80 40.6    81.8 51.93
## 5       baio     2     75 42.7    81.3 51.34
## 6       baio     3     76 41.1    81.1 47.78</pre>
</div>
<p>Now, we consider the whole lot of measures and calculate the correlation coefficient between yield and TKW; we also use a t-test for the hypothesis that the correlation coefficient is equal to 0. This is the result:</p>
<div class="cell">
<pre>ra &lt;- with(dataset, rcorr(Yield, TKW) )
# 
# Correlation coefficient
ra$r[1,2] 
## [1] 0.540957
#
# P-level
ra$P[1,2] 
## [1] 1.850931e-07</pre>
</div>
<p>The conclusion is: the correlation coefficient is positive and significantly different from 0, which supports the idea that plots with a high value on yield tend to have a high value on TKW, as well.</p>
<p>Such an approach is very often seen also in high ranking Journals, but: is it correct? Can we trust the conclusions? The answer is no, not completely. And here is why. Please, note that in this post I am following the point made by Bland and Altman (1994), by using an example that is more closely related to plant/crop science.</p>
<section id="statistical-testing-is-invalid" class="level1">
<h1>Statistical testing is invalid</h1>
<p>The correlation coefficient is a measure of joint variability and makes some basic assumptions (e.g. linearity), which I have detailed elsewhere (<a href="https://www.statforbiology.com/2019/stat_general_correlation_alookat/" rel="nofollow" target="_blank">see here</a>). In particular, the t-test requires that all the couples of observations are taken on independent subjects.</p>
<p>In our case, the 81 plots are not independent, as they are grouped by block and genotype, so that observations in the same block or for the same genotype are more alike than observations in different blocks and different genotypes (intra-class correlation). Consequently, the t-test is invalid and <strong>we are not allowed to conclude that the correlation is significant, because such a conclusion is not necessarily supported by the data</strong>.</p>
<p>Unfortunately, such lack of independence relates to all designed experiments, which seems to be a fact that goes largely unnoticed. <strong>In general correlation coefficients calculated for raw data from designed experiments, cannot be submitted to t-tests, without accounting for the effects of all treatment and grouping factors.</strong></p>
</section>
<section id="correlation-is-not-unique" class="level1">
<h1>Correlation is not unique</h1>
<p>Apart from statistical testing, the correlation coefficient we have calculated above (0.541) maintain its value as a descriptive measure of joint variability. But are we sure that using the raw observed data is the best way to go?</p>
<p>For one, such correlation coefficient highlights that yield and TKW are positively correlated, but is this true for all genotypes and for all blocks? Let’s check this.</p>
<div class="cell">
<pre># Correlations within genotypes
wgCors &lt;- by(dataset, list(dataset$Genotype), function(x) cor(x$Yield, x$TKW))
wgCors
## : arcobaleno
## [1] 0.9847228
## ------------------------------------------------------------ 
## : baio
## [1] 0.1611952
## ------------------------------------------------------------ 
## : claudio
## [1] -0.9993872
## ------------------------------------------------------------ 
## : colorado
## [1] 0.9837293
## ------------------------------------------------------------ 
## : colosseo
## [1] 0.4564855
## ------------------------------------------------------------ 
## : creso
## [1] -0.5910193
## ------------------------------------------------------------ 
## : duilio
## [1] -0.988233
## ------------------------------------------------------------ 
## : gianni
## [1] -0.7603802
## ------------------------------------------------------------ 
## : giotto
## [1] 0.9520211
## ------------------------------------------------------------ 
## : grazia
## [1] 0.4980828
## ------------------------------------------------------------ 
## : iride
## [1] 0.7563338
## ------------------------------------------------------------ 
## : meridano
## [1] 0.1174342
## ------------------------------------------------------------ 
## : neodur
## [1] 0.4805871
## ------------------------------------------------------------ 
## : orobel
## [1] 0.8907754
## ------------------------------------------------------------ 
## : pietrafitta
## [1] -0.9633891
## ------------------------------------------------------------ 
## : portobello
## [1] 0.9210135
## ------------------------------------------------------------ 
## : portofino
## [1] -0.9900764
## ------------------------------------------------------------ 
## : portorico
## [1] 0.1394211
## ------------------------------------------------------------ 
## : preco
## [1] 0.9007067
## ------------------------------------------------------------ 
## : quadrato
## [1] -0.5840238
## ------------------------------------------------------------ 
## : sancarlo
## [1] -0.646067
## ------------------------------------------------------------ 
## : simeto
## [1] -0.4051779
## ------------------------------------------------------------ 
## : solex
## [1] -0.6066363
## ------------------------------------------------------------ 
## : terrabianca
## [1] -0.4076416
## ------------------------------------------------------------ 
## : verdi
## [1] 0.5801404
## ------------------------------------------------------------ 
## : vesuvio
## [1] -0.7797493
## ------------------------------------------------------------ 
## : vitromax
## [1] -0.8056514

# Correlations within blocks
wbCors &lt;- by(dataset, list(dataset$Block), function(x) cor(x$Yield, x$TKW))
wbCors
## : 1
## [1] 0.5998137
## ------------------------------------------------------------ 
## : 2
## [1] 0.539999
## ------------------------------------------------------------ 
## : 3
## [1] 0.5370398</pre>
</div>
<p>As for the genotypes, we have 27 correlation coefficients, ranging from -0.999 to 0.985. We only have three couples of measurements per genotype and it is clear that this is not much, to reliably estimate a correlation coefficient. However, it is enough to be suspicious about the extent of correlation between yield and TKW, as it may depend on the genotype.</p>
<p>On the other hand, the correlation within blocks is more constant, independent on the block and similar to the correlation between plots.</p>
<p>It may be interesting to get an estimate of the average within-group correlation. To this aim, we can perform two separate ANOVAs (one per trait), including all relevant effects (blocks and genotypes) and calculate the correlation between the residuals:</p>
<div class="cell">
<pre>mod1 &lt;- lm(Yield ~ factor(Block) + Genotype, data = dataset)
mod2 &lt;- lm(TKW ~ factor(Block) + Genotype, data = dataset)
wCor &lt;- rcorr(residuals(mod1), residuals(mod2))
wCor$r
##            x          y
## x 1.00000000 0.03133109
## y 0.03133109 1.00000000
wCor$P
##           x         y
## x        NA 0.7812693
## y 0.7812693        NA</pre>
</div>
<p>The average within-group correlation is very small and unsignificant. Let’s think about this correlation: residuals represent yield and TKW values for all plots, once the effects of blocks and genotypes have been removed. A high correlation of residuals would mean that, letting aside the effects of the block and genotype to which it belongs, a plot with a high value on yield also shows a high value on TKW. <strong>The correct conclusion, indeed, is that, once the effects of treatment ang grouping factors have been removed, there is no correlation between the traits under study.</strong></p>
<p>But, this is not enough to stop our investigations. We could also consider the means for genotypes/blocks and see whether they are correlated. Blocks and genotypes are independent and, in principle, significance testing is permitted. However, this is not recommended with block means, as three data are too few to make any tests.</p>
<div class="cell">
<pre>means &lt;- aggregate(dataset[,c(4,6)], list(dataset$Genotype), mean)
rgPear &lt;- rcorr( as.matrix(means[,2:3]) )
rgPear$r
##             TKW     Yield
## TKW   1.0000000 0.5855966
## Yield 0.5855966 1.0000000
rgPear$P
##              TKW      Yield
## TKW           NA 0.00133149
## Yield 0.00133149         NA

means &lt;- aggregate(dataset[,c(4,6)], list(dataset$Block), mean)
rbPear &lt;- cor( as.matrix(means[,2:3]) )
rbPear
##               TKW       Yield
## TKW    1.00000000 -0.08812544
## Yield -0.08812544  1.00000000</pre>
</div>
<p>We note that the correlation between genotype means is high and significant. On the contrary, the correlation between block means is near to 0.</p>
</section>
<section id="and-so-what" class="level1">
<h1>And so what?</h1>
<p>At this stage, you may be confused… Let’s try to clear the fog.</p>
<p>A naive correlation analysis on raw data brought us to the conclusion that yield and TKW are significantly correlated. Unfortunately, such a conclusion is overly simplistic and it is hardly supported by the data.</p>
<p>A more careful process of data analysis should not neglect that in every designed field experiment we have groups of subjects and there are several possible types of correlation:</p>
<ol type="1">
<li>correlation between plot measurements (naive correlation)</li>
<li>correlation between the residuals</li>
<li>correlation between treatment/block means</li>
</ol>
<p>All these correlations should be investigated and used for interpretation.</p>
<ol type="1">
<li>The ‘naive’ correlation between the plot measurements is very easily calculated, but it is grossy misleading. Indeed, it disregards the treatment/block effects and it does not permit hypotheses testing, as the subjects are not independent. <strong>I think it is reasonable to suggest that the ‘naive’ correlation coefficient is never used for interpretation</strong>.</li>
<li>The correlation between the residuals is a reliable measure of joint variation, because the experimental design is adequately referenced, by removing the effects of tratments/blocks. In this example, residuals are not correlated. Further analyses show that the correlation between yield and TKW, if any, may depend on the genotype, while it does not depend on the block.</li>
<li>The correlation between treatment/block means permits to assess whether the treatment/block effects on the two traits are correlated. In this case, while we are not allowed to conclude that yield and TKW are, in general, correlated, we can conclude that the genotypes with a high level of yield also show a high level of TKW.</li>
</ol>
<p>An elegant and advanced method to tackle correlation analyses on data from designed field experiments has been put forward by Piepho (2018), within the frame of mixed models. But this deserves another post.</p>
<p>Thanks for reading, and happy coding!</p>
<p>(P.S. If you enjoyed this post, check out my book “Field Research Methods in Agriculture: An Introduction with R” via the link below!)</p>
<p>Prof. Andrea Onofri<br>
Department of Agricultural, Food and Environmental Sciences<br>
University of Perugia (Italy)<br>
Send comments to: <a href="mailto:andrea.onofri@unipg.it" rel="nofollow" target="_blank">andrea.onofri@unipg.it</a></p>
<p><a href="https://www.awin1.com/cread.php?awinmid=26429&#038;awinaffid=2675822&#038;ued=https%3A%2F%2Flink.springer.com%2Fbook%2F10.1007%2F978-3-032-08199-5" rel="nofollow" target="_blank"><img src="https://i0.wp.com/www.statforbiology.com/Figures/Email_Signature_978-3-032-08199-5.png?w=578&#038;ssl=1" alt="Book cover" class="cover" align="center" data-recalc-dims="1"></a></p>
<hr>
</section>
<section id="references" class="level1">
<h1>References</h1>
<ol type="1">
<li>Bland, J.M., Altman, D.G., 1994. Statistics Notes: Correlation, regression, and repeated data. BMJ 308, 896–896.</li>
<li>Piepho, H.-P., 2018. Allowing for the structure of a designed experiment when estimating and testing trait correlations. The Journal of Agricultural Science 156, 59–70.</li>
</ol>
<hr>
<p>Originally published on 2019-04-30</p>


</section>

 
<div style="border: 1px solid; background: none repeat scroll 0 0 #EDEDED; margin: 1px; font-size: 13px;">
<div style="text-align: center;">To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://www.statforbiology.com/posts/general_CorrelationIndependence1.html"> Statforbiology</a></strong>.</div>
<hr />
<a href="https://www.r-bloggers.com/" rel="nofollow">R-bloggers.com</a> offers <strong><a href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers" rel="nofollow">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" href="https://www.r-project.org/" rel="nofollow">R</a> news and tutorials about <a title="R tutorials" href="https://www.r-bloggers.com/how-to-learn-r-2/" rel="nofollow">learning R</a> and many other topics. <a title="Data science jobs" href="https://www.r-users.com/" rel="nofollow">Click here if you're looking to post or find an R/data-science job</a>.

<hr>Want to share your content on R-bloggers?<a href="https://www.r-bloggers.com/add-your-blog/" rel="nofollow"> click here</a> if you have a blog, or <a href="http://r-posts.com/" rel="nofollow"> here</a> if you don't.
</div><strong>Continue reading</strong>: <a href="https://www.r-bloggers.com/2026/10/can-we-trust-the-correlation-coefficient-in-designed-field-experiments/">Can we trust the correlation coefficient in designed field experiments?</a>]]></content:encoded>
					
		
		<enclosure url="" length="0" type="" />

		<post-id xmlns="com-wordpress:feed-additions:1">404261</post-id>	</item>
	</channel>
</rss>
